Showing posts with label c# 14. Show all posts
Showing posts with label c# 14. Show all posts

Sunday, 23 November 2025

What's New in .NET 10?

What's New in .NET 10?

What's New in .NET 10?

.NET 10 brings groundbreaking performance improvements, cloud-native features, AI integration, and developer productivity enhancements for modern application development.

What's New in .NET 10 header

.NET 10 represents a major milestone in Microsoft's unified platform evolution, delivering unprecedented performance, seamless cloud-native development, integrated AI capabilities, and enhanced developer experiences. Released in November 2025, .NET 10 builds upon the solid foundation of previous releases while introducing innovative features that push the boundaries of what's possible with modern application development.

This comprehensive guide explores the key enhancements across runtime performance, C# 14 language features, ASP.NET Core improvements, cloud-native tooling, MAUI updates, Blazor advancements, and AI integration—everything you need to leverage .NET 10's full potential.

1. Performance & Runtime Improvements

Performance and runtime improvements illustration

Core Performance Enhancements:

  • JIT Compiler Optimizations: 15-25% faster startup times through improved tiered compilation and profile-guided optimization (PGO).
  • GC Improvements: Dynamic Adaptive to Application Size (DATAS) reduces memory footprint by 20-30% for microservices.
  • Native AOT Enhancements: Full support for ASP.NET Core minimal APIs with 70% smaller deployment size.
  • SIMD Improvements: Enhanced vectorization with AVX-512 support for numerical and data processing workloads.

Startup Performance

These improvements shine in short-lived processes like CLI tools, serverless functions, and small microservices where every millisecond of cold start matters.

  • Assembly loading optimization: Non-essential assemblies are loaded lazily so your minimal API can start serving simple requests while heavy features load in the background.
  • Improved R2R compilation: Ready-to-Run images reduce JIT work on first request, cutting cold-start time for containerized web apps.
  • Lazy initialization patterns: Large caches and background services can be wired to initialize only when actually needed, improving initial responsiveness.

Throughput

Designed for busy APIs, background workers, and real-time systems where sustained requests per second are critical.

  • Enhanced inlining heuristics: Hot methods in routing, JSON serialization, and validation are more aggressively inlined, reducing call overhead.
  • Better loop optimizations: Common loops over arrays and Span<T> are vectorized, boosting performance for log processing and ETL-like workloads.
  • Reduced allocation overhead: Fewer temporary objects are created in core libraries, reducing GC pressure and increasing sustained throughput.

Memory Efficiency

Especially useful when running many instances per node in Kubernetes or on small cloud SKUs.

  • Span<T> and Memory<T> optimizations: IO, networking, and text APIs rely more on spans, avoiding unnecessary copies.
  • Reduced GEN0/GEN1 collections: Allocation patterns in the BCL and ASP.NET Core have been tuned to allocate less and promote less, leading to fewer short GC pauses.
  • Better heap compaction algorithms: Long-running services stay stable over days of uptime with less fragmentation and more predictable memory usage.
// Example: Native AOT with minimal API
using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello .NET 10!");
app.Run();

// Build with: dotnet publish -c Release /p:PublishAot=true
// Result: ~8MB executable, <50ms startup time

💡 Benchmark: Web API throughput increased by 40% compared to .NET 9, with memory usage reduced by 25% in containerized scenarios.

🎯 2. C# 14 Language Features

C# 14 language features illustration

.NET 10 ships with C# 14, bringing powerful new language features that enhance developer productivity and code clarity:

Extension Members

Add properties and indexers to existing types via extension blocks:

extension StringExtensions for string
{
    public bool IsValidEmail =>
        this.Contains("@");
    
    public string this[Range range] =>
        this[range];
}

Field Keyword in Properties

Auto-generated backing fields directly in property initializers:

public string Name
{
    get => field;
    set => field = value.Trim();
} = string.Empty;

Unbound Generic Types

Use open generic types in nameof expressions:

// Now valid in C# 14
var name = nameof(List<>);
var method = nameof(Enumerable
    .Select<,>);

Improved Pattern Matching

Enhanced list patterns and recursive patterns:

if (numbers is [var first, 
    .., var last])
{
    Console.WriteLine(
        $"{first}...{last}");
}

Additional C# 14 Features:

  • Null-Conditional Assignment: obj?.Property ??= defaultValue
  • Implicit Span Conversions: Automatic conversion of arrays to ReadOnlySpan<T>
  • Collection Expressions: Enhanced syntax with spread operators
  • Lambda Improvements: Natural delegate types and async improvements

🌐 3. ASP.NET Core Enhancements

ASP.NET Core enhancements illustration

ASP.NET Core 10 delivers significant improvements for web APIs, Blazor applications, and cloud-native services.

Minimal APIs 2.0

// Enhanced minimal APIs with automatic OpenAPI generation
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi(); // New built-in OpenAPI support

var app = builder.Build();

app.MapGet("/products/{id}", 
    (int id, IProductService service) => 
        service.GetProduct(id))
    .WithName("GetProduct")
    .WithTags("Products")
    .Produces(200)
    .ProducesValidationProblem();

app.MapOpenApi(); // Serves OpenAPI spec
app.Run();

HTTP/3 by Default

  • QUIC protocol support enabled
  • Improved multiplexing
  • Better mobile connectivity

Built-in Rate Limiting

  • Token bucket algorithm
  • Sliding window policies
  • Distributed scenarios support

Output Caching

  • Memory and distributed caching
  • Tag-based invalidation
  • Compression integration

Request Decompression

  • Automatic content decompression
  • Gzip, Brotli, Deflate support
  • Configurable size limits

Blazor United

Unified Rendering Modes: Blazor 10 introduces "Blazor United" - seamless switching between Server, WebAssembly, and SSR (Static Server Rendering) per component:

@* Per-component rendering mode *@
@page "/products"
@rendermode InteractiveServer


  • Enhanced Form Handling: Built-in validation with FluentValidation integration
  • Streaming Rendering: Progressive SSR for improved perceived performance
  • Hot Reload Improvements: Faster iteration with enhanced Hot Reload capabilities
  • QuickGrid Enhancements: Advanced data grid with virtualization and custom templates

☁️ 4. Cloud-Native & Container Features

Cloud-native and container features illustration

.NET 10 doubles down on cloud-native development with enhanced container support, observability, and orchestration integration.

Container Optimization

Feature Description Benefit
Chiseled Containers Ultra-minimal Ubuntu-based images 40% smaller size, reduced attack surface
Multi-arch Images Native ARM64 and x64 support Optimized for various cloud platforms
Layer Optimization Intelligent layer caching Faster builds and deployments
Distroless Support Google distroless base images Maximum security, minimal footprint

Observability Enhancements

OpenTelemetry observability
// Built-in OpenTelemetry integration
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddOtlpExporter());

// Automatic distributed tracing and metrics collection

Health Checks 2.0

  • Kubernetes liveness/readiness probes
  • Custom health check authoring
  • Dependency health tracking

Resilience Patterns

  • Built-in retry policies
  • Circuit breaker patterns
  • Timeout and fallback strategies

Configuration

  • Azure App Configuration support
  • Kubernetes ConfigMaps/Secrets
  • Hot reload configuration changes

🤖 5. AI & Machine Learning Integration

AI and ML integration illustration

.NET 10 brings AI to the forefront with native integration of large language models, semantic search, and intelligent application features.

Semantic Kernel Integration

using Microsoft.SemanticKernel;

var kernel = Kernel.CreateBuilder()
    .AddAzureOpenAIChatCompletion(
        deploymentName: "gpt-4",
        endpoint: "https://...",
        apiKey: "...")
    .Build();

// Use AI in your application
var result = await kernel.InvokePromptAsync(
    "Summarize the following text: {{$input}}",
    new() { ["input"] = longText });

Console.WriteLine(result);

Vector Search

  • Built-in embedding generation
  • Similarity search capabilities
  • Integration with vector databases

ML.NET 4.0

  • Enhanced AutoML capabilities
  • ONNX runtime improvements
  • Deep learning model support

AI Building Blocks

  • Text classification
  • Sentiment analysis
  • Named entity recognition

Responsible AI

  • Bias detection tools
  • Explainability features
  • Privacy-preserving ML

💡 Use Case: Build intelligent chatbots, document Q&A systems, and semantic search features with just a few lines of code using Semantic Kernel and Azure OpenAI.

📱 6. .NET MAUI Evolution

MAUI multi-device UI illustration

.NET Multi-platform App UI (MAUI) 10 delivers enhanced cross-platform development for mobile and desktop applications, with a strong focus on startup time, smooth UI, and deeper access to native platform features.

Performance

MAUI 10 makes cross-platform apps feel truly native by cutting down startup time and reducing UI jank.

  • 50% faster app startup on Android: Precompiled XAML and optimized resource loading mean your home screen appears noticeably quicker on real devices.
  • Improved rendering pipeline: The new layout and drawing pipeline reduces overdraw and unnecessary layout passes, giving smoother scrolling in list-heavy pages.
  • Reduced memory footprint: Image and font caching are smarter, so long-running apps (chat, dashboards, POS) leak less memory over time.

Controls & UI

Modern design systems out of the box, with more polished controls and fewer custom renderers to maintain.

  • Material Design 3 support: Android apps pick up updated elevation, shapes, and color schemes automatically when you use standard MAUI controls.
  • Fluent Design integration (Windows): Desktop apps feel at home on Windows 11 with updated styles, rounded corners, and system accent colors.
  • Enhanced data grid control: Virtualization, sorting, and templating improvements make it easier to build CRUD admin screens and reporting dashboards.

Desktop Features

Turn a mobile-first MAUI app into a serious desktop citizen without rewriting the UI stack.

  • Window management APIs: Open secondary windows for chat, inspector, or tool panes, and control size/position programmatically.
  • System tray integration: Build utilities that minimize to tray, show status icons, and respond to quick actions from the tray menu.
  • Multi-window support: On macOS and Windows, allow users to open multiple documents or views side by side, like a traditional desktop app.

Platform APIs

Access to deeper device capabilities with a consistent .NET API surface.

  • Camera and media enhancements: Simplified photo/video capture APIs make it easier to build apps for inspections, deliveries, and social content.
  • Biometric authentication: Use Face ID, Touch ID, or Windows Hello for secure login flows with a single abstraction layer.
  • Background tasks support: Schedule background syncs or push-notification handlers that behave correctly across iOS, Android, Windows, and macOS.
// Example: Cross-platform file picker
using Microsoft.Maui.Storage;

public async Task PickFileAsync()
{
    var result = await FilePicker.PickAsync(new PickOptions
    {
        PickerTitle = "Select a document",
        FileTypes = new FilePickerFileType(new Dictionary>
        {
            { DevicePlatform.iOS, new[] { "public.pdf" } },
            { DevicePlatform.Android, new[] { "application/pdf" } },
            { DevicePlatform.WinUI, new[] { ".pdf" } },
            { DevicePlatform.MacCatalyst, new[] { "pdf" } }
        })
    });

    return result?.FullPath;
}

🛠️ 7. Developer Productivity

Developer productivity illustration

.NET 10 introduces numerous enhancements that streamline development workflows and improve the overall developer experience.

Enhanced Tooling

  • Visual Studio 2026: AI-powered code completions, intelligent refactoring suggestions
  • VS Code C# Dev Kit: Full .NET debugging, Hot Reload, and project management
  • GitHub Copilot Integration: .NET-aware code generation and documentation
  • dotnet CLI Enhancements: Improved templates, better error messages, interactive mode

Developer Experience Improvements

// New dotnet CLI features
dotnet new list --ai                    // AI-suggested project templates
dotnet add package --suggest            // Package recommendation
dotnet watch --interactive              // Interactive Hot Reload mode
dotnet analyze --fix                    // Auto-fix code issues
dotnet test --coverage --format=cobertura  // Built-in code coverage

Hot Reload Everywhere

  • Blazor (all render modes)
  • MAUI applications
  • ASP.NET Core apps
  • Console applications

Diagnostics

  • Improved exception messages
  • Better stack traces
  • Performance profiling integration

Testing

  • Parallel test execution
  • Snapshot testing support
  • Integration test improvements

🔒 8. Security Enhancements

Security enhancements illustration

Security-first approach with enhanced cryptography, authentication, and protection mechanisms.

  • Cryptography APIs: Support for newer algorithms (ChaCha20-Poly1305, EdDSA)
  • Authentication: Enhanced OAuth 2.1 support, FIDO2/WebAuthn integration
  • Data Protection: Improved key management, Azure Key Vault integration
  • Secure Defaults: TLS 1.3 by default, stronger cipher suites
  • Supply Chain Security: Package signing verification, SBOM generation

⚠️ Breaking Change: TLS 1.0/1.1 support removed. Upgrade clients to TLS 1.2+ before migrating to .NET 10.

🔄 9. Migration & Compatibility

Migration and compatibility illustration

Upgrading from .NET 9

// Update project file

  
    net10.0
    enable
    enable
  


// Run upgrade assistant
dotnet tool install -g upgrade-assistant
dotnet upgrade-assistant upgrade MyApp.sln --target-tfm net10.0

💡 Compatibility: .NET 10 maintains excellent backward compatibility with .NET 8/9. Most applications can upgrade with minimal changes.

Common Migration Tasks

Area Action Required Impact
NuGet Packages Update to .NET 10 compatible versions Medium
TLS Protocols Ensure clients support TLS 1.2+ High
APIs Review deprecated API warnings Low
Runtime Test performance and memory usage Low

📚 10. Best Practices & Recommendations

Best practices and recommendations infographic

Maximize .NET 10 Benefits:

  • Enable Native AOT: For microservices and containerized apps to reduce startup time and memory
  • Adopt Minimal APIs: Simpler, faster APIs with built-in OpenAPI documentation
  • Use Output Caching: Significantly improve API performance with proper caching strategies
  • Implement Observability: Use OpenTelemetry for comprehensive monitoring
  • Leverage AI Features: Integrate Semantic Kernel for intelligent application features
  • Containerize Efficiently: Use chiseled containers for production deployments

Performance Optimization Checklist:

  1. Enable PGO (Profile-Guided Optimization) in Release builds
  2. Use Span<T> and Memory<T> for high-performance scenarios
  3. Configure GC for your workload (Server vs Workstation)
  4. Implement proper async/await patterns
  5. Use ValueTask<T> for frequently-called async methods
  6. Enable HTTP/3 for better network performance

💡 Pro Tips:

  • Use dotnet-counters and dotnet-trace for production diagnostics
  • Leverage source generators for compile-time code generation
  • Adopt nullable reference types for better null safety
  • Use record types for immutable data models
  • Implement health checks for all external dependencies

🎯 Conclusion

Key Takeaways:

  • .NET 10 delivers 15-40% performance improvements across various scenarios
  • C# 14 introduces powerful language features that enhance productivity
  • Cloud-native development is simplified with enhanced container support and observability
  • AI integration through Semantic Kernel opens new possibilities for intelligent applications
  • Blazor United provides flexible rendering options for web applications
  • MAUI continues to evolve as a robust cross-platform framework
  • Developer experience improvements make .NET 10 the most productive release yet

Getting Started:

  1. Download .NET 10 SDK from dot.net
  2. Update Visual Studio 2026 or VS Code with latest C# Dev Kit
  3. Explore sample projects: dotnet new list
  4. Review migration guide for existing applications
  5. Join the .NET community on GitHub and Discord

.NET 10 represents a significant leap forward in modern application development. Whether you're building cloud-native microservices, cross-platform mobile apps, AI-powered applications, or high-performance web APIs, .NET 10 provides the tools, performance, and features to bring your vision to life.

Start exploring .NET 10 today and experience the future of .NET development!

🔗 References & Further Reading

For deeper, always up-to-date details on .NET 10 and related technologies, refer to the official Microsoft documentation and learning resources below:

These links point to the official Microsoft docs portal, which is continuously updated as new .NET 10 features evolve and service releases ship. Readers can bookmark them to stay current with best practices and platform changes.

Monday, 3 November 2025

What's new in c# 14?



C# 14 — A Leap Toward Cleaner, Smarter Code

Exploring the most impactful features with practical examples

C# 14 marks a thoughtful evolution in the language, focusing on expressiveness, modularity, and developer productivity. While not a radical overhaul, this release introduces subtle yet powerful enhancements that make everyday coding more intuitive and maintainable. Whether you're building high-performance systems or crafting elegant APIs, C# 14 helps you write less boilerplate and more meaningful logic.

In this guide, we'll explore the most impactful features of C# 14, with practical examples and insights into how they improve real-world development.

🧭 C# 14 Feature Highlights

Here are the key features we'll be diving into:

  1. Extension Members — Add properties and indexers to existing types via extension blocks.
  2. Field Keyword in Properties — Access auto-generated backing fields directly with field.
  3. Null-Conditional Assignment (??=) — Assign only when the target is null.
  4. Unbound Generic Types in nameof — Use open generic types for cleaner diagnostics.
  5. Implicit Span Conversions — Seamless transitions between Span<T> and ReadOnlySpan<T>.
  6. Lambda Parameter Modifiers — Use ref, in, or out in lambda expressions.
  7. Partial Events and Constructors — Modularize event and constructor logic across files.
  8. User-Defined Compound Assignment Operators — Customize behavior for +=, -=, etc.
  9. Improved Interpolated Strings — Enhanced formatting and performance for string interpolation.
  10. Collection Expressions (Preview) — Concise syntax for initializing collections.

1. Extension Members

🔧 What Are Extension Members?

Traditionally, C# extension methods let you "add" methods to existing types without modifying them. But with C# 14, you can now define:

  • Properties
  • Indexers
  • Events
  • Even operators

…all inside an extension block scoped to a specific type.

🧪 Example: Extension Methods for IEnumerable<T>

public static class EnumerableExtensions
{
    // Method: Check if the collection is empty
    public static bool IsEmpty<T>(this IEnumerable<T> source)
        => !source.Any();

    // Method: Access element by index safely (returns default if out of range)
    public static T? ElementAtOrDefaultSafe<T>(this IEnumerable<T> source, int index)
        => source.Skip(index).DefaultIfEmpty(default!).FirstOrDefault();

    // Method: Get the last element safely
    public static T? LastOrDefaultSafe<T>(this IEnumerable<T> source)
        => source.LastOrDefault();
}

💡 Usage Example

var numbers = new[] { 1, 2, 3, 4, 5 };

// Using the extension methods
if (!numbers.IsEmpty())
{
    Console.WriteLine($"Third element: {numbers.ElementAtOrDefaultSafe(2)}");
    Console.WriteLine($"Last element: {numbers.LastOrDefaultSafe()}");
}

💡 Key Benefits:

  • Cleaner API surface — Access properties and indexers naturally without method calls
  • Better IntelliSense — IDE suggestions now include extension properties and indexers
  • Type safety — Compile-time checks ensure proper usage

2. Field Keyword in Properties

🔑 Direct Access to Backing Fields

The new field keyword allows you to access the auto-generated backing field directly in property accessors, eliminating the need to manually declare private fields.

🧪 Example: Smart Properties with Validation

public class Product
{
    public string Name { get; set; }

    public decimal Price
    {
        get => field;
        set
        {
            if (value < 0)
                throw new ArgumentException("Price cannot be negative");
            field = value;
        }
    }

    public int Quantity
    {
        get => field;
        set
        {
            field = Math.Max(0, value); // Ensure non-negative
            Console.WriteLine($"Stock updated: {field} units");
        }
    }
}

💡 Why This Matters: Before C# 14, you had to declare a separate backing field and manually manage it. Now, you can add validation and logging to auto-properties without the boilerplate!

3. Null-Conditional Assignment (??=)

🎯 Assign Only When Null

The ??= operator assigns a value to a variable only if it's currently null. This simplifies lazy initialization patterns.

private Logger? _logger;

public Logger Logger
{
    get
    {
        // Initialize only if null
        _logger ??= new Logger();
        return _logger;
    }
}

Comparison with traditional approach:

// Old way
if (_logger == null)
{
    _logger = new Logger();
}

// New way
_logger ??= new Logger();

4. Unbound Generic Types in nameof

📝 What Are Unbound Generic Types?

Prior to C# 14, you couldn't use nameof with open generic types directly. You had to specify type parameters even when you just wanted the type's name. Now, you can reference unbound generic types cleanly.

🧪 Example: Cleaner Type Name References

// Before C# 14 - Had to provide type arguments
string typeName = nameof(Dictionary<string, int>); // "Dictionary"

// C# 14 - Can use unbound generic types
string typeName = nameof(Dictionary<,>); // "Dictionary"
string listName = nameof(List<>);       // "List"

💡 Real-World Usage: Generic Factory with Logging

public class GenericFactory<T> where T : new()
{
    public T Create()
    {
        // Log the factory name without specifying T
        var factoryName = nameof(GenericFactory<>);
        Console.WriteLine($"Creating instance in {factoryName}");
        
        return new T();
    }
}

// Usage in error handling
public void ProcessRepository<TRepo, TEntity>()
{
    try
    {
        // Processing logic...
    }
    catch (Exception ex)
    {
        // Clean error messages without type parameters
        throw new InvalidOperationException(
            $"Failed in {nameof(ProcessRepository<,>)}", ex);
    }
}

💡 Key Benefits:

  • Cleaner code — No need to provide arbitrary type arguments just to get a name
  • Better refactoring — Generic type names update automatically when renamed
  • Improved diagnostics — More readable error messages and logs
  • API documentation — Reference generic types clearly in generated docs

📊 Use Cases

  • Logging: Log generic type names without concrete type parameters
  • Diagnostics: Create meaningful exception messages referencing generic methods
  • Reflection: Build type metadata utilities more elegantly
  • Code generation: Generate code based on generic type names

5. Implicit Span Conversions

Seamless Span Type Transitions

C# 14 introduces implicit conversions between Span<T> and ReadOnlySpan<T>, making it much easier to work with high-performance memory operations. You can now pass Span<T> to methods expecting ReadOnlySpan<T> without explicit casting.

🧪 Example: Before vs. After

// Before C# 14 - Manual casting required
void ProcessData(ReadOnlySpan<byte> data)
{
    // Process read-only data
    Console.WriteLine($"Processing {data.Length} bytes");
}

Span<byte> buffer = new byte[1024];
ProcessData(buffer); // ❌ Compiler error in older versions

// Had to explicitly cast:
ProcessData((ReadOnlySpan<byte>)buffer);

// C# 14 - Implicit conversion
Span<byte> buffer = new byte[1024];
ProcessData(buffer); // ✅ Works seamlessly!

💡 Real-World Example: Stream Processing

public class DataProcessor
{
    // Method that reads data without modifying it
    public int CalculateChecksum(ReadOnlySpan<byte> data)
    {
        int checksum = 0;
        foreach (var b in data)
        {
            checksum ^= b;
        }
        return checksum;
    }

    // Method that modifies data
    public void EncryptData(Span<byte> data, byte key)
    {
        for (int i = 0; i < data.Length; i++)
        {
            data[i] ^= key;
        }
    }

    public void ProcessStream()
    {
        Span<byte> buffer = stackalloc byte[512];
        
        // Fill buffer with data...
        
        // Implicit conversion to ReadOnlySpan<byte>
        var checksum = CalculateChecksum(buffer); // ✅ Works!
        
        // Modify the buffer
        EncryptData(buffer, 0x5A);
        
        // Calculate checksum again
        var newChecksum = CalculateChecksum(buffer); // ✅ Still works!
        
        Console.WriteLine($"Checksums: {checksum} → {newChecksum}");
    }
}

🎯 Performance Benefits

// Stack-allocated buffer for zero-allocation processing
public static bool ValidateData(ReadOnlySpan<char> input)
{
    return input.Length > 0 && input[0] == '{' && input[^1] == '}';
}

// Can now pass strings directly!
string json = "{\"name\":\"John\"}";
if (ValidateData(json)) // Implicit string → ReadOnlySpan<char>
{
    Console.WriteLine("Valid JSON structure");
}

// Stack-allocated buffer also works
Span<char> buffer = stackalloc char[100];
int length = FormatData(buffer);
if (ValidateData(buffer.Slice(0, length))) // Span → ReadOnlySpan
{
    Console.WriteLine("Buffer validated");
}

💡 Key Benefits:

  • Zero-copy operations — Pass data without allocations or copying
  • Cleaner API design — Methods can accept ReadOnlySpan for immutability guarantees
  • Better performance — Stack-allocated buffers with minimal overhead
  • Improved interop — Easier integration with native code and unsafe contexts
  • Type safety — Compiler enforces read-only vs. mutable semantics

📋 Common Patterns

// Pattern 1: Parse without allocation
public static int ParseNumber(ReadOnlySpan<char> text)
{
    return int.Parse(text);
}

string input = "12345";
var number = ParseNumber(input); // No substring allocation!

// Pattern 2: Slice and process
void ProcessSegments(ReadOnlySpan<char> data)
{
    var firstPart = data.Slice(0, 10);
    var secondPart = data.Slice(10);
    // Process without creating new strings
}

// Pattern 3: Safe buffer operations
void SafeReadBuffer()
{
    Span<byte> writeBuffer = new byte[100];
    FillBuffer(writeBuffer);
    
    // Pass as read-only to prevent modifications
    VerifyBuffer(writeBuffer); // Implicit → ReadOnlySpan
}

6. Lambda Parameter Modifiers

🔄 Enhanced Lambda Expressions

C# 14 allows you to use parameter modifiers (ref, in, out) in lambda expressions, enabling more efficient parameter passing and better performance in high-throughput scenarios.

🧪 Example: Using ref in Lambdas

// Before C# 14 - Had to use delegates explicitly
delegate void RefAction<T>(ref T value);

void ProcessWithRef(RefAction<int> action)
{
    int value = 10;
    action(ref value);
    Console.WriteLine(value);
}

// C# 14 - Use ref directly in lambda
var incrementer = (ref int x) => x++;

int number = 5;
incrementer(ref number);
Console.WriteLine(number); // Output: 6

💡 Real-World Example: High-Performance Data Processing

public class DataTransformer
{
    // Transform large structs in-place without copying
    public void TransformInPlace<T>(
        List<T> items,
        RefAction<T> transformer) where T : struct
    {
        for (int i = 0; i < items.Count; i++)
        {
            var temp = items[i];
            transformer(ref temp);
            items[i] = temp;
        }
    }
}

// Usage: Transform points without allocation
struct Point
{
    public double X, Y;
}

var points = new List<Point> { new() { X = 1, Y = 2 } };
var transformer = new DataTransformer();

// Lambda with ref modifier - modifies in place
transformer.TransformInPlace(points, (ref Point p) => 
{
    p.X *= 2;
    p.Y *= 2;
});

📊 Using in for Read-Only References

// Use 'in' to pass large structs efficiently without copying
struct Matrix4x4
{
    public double[,] Data;
    // ... large struct with 16 doubles
}

// Custom delegate type with 'in' parameter
public delegate double InFunc<T>(in T value);

// Lambda with 'in' parameter - read-only, no copy
InFunc<Matrix4x4> calculateDeterminant = 
    (in Matrix4x4 matrix) =>
    {
        // Access matrix without copying the entire struct
        return matrix.Data[0, 0] * matrix.Data[1, 1];
    };

var matrix = new Matrix4x4();
var det = calculateDeterminant(in matrix);

🎯 Using out for Output Parameters

// Custom delegate type with 'out' parameter
public delegate bool TryParseFunc<T>(string input, out T result);

// Lambda that returns multiple values via out parameter
TryParseFunc<int> tryParse = 
    (string input, out int result) =>
    {
        return int.TryParse(input, out result);
    };

if (tryParse("42", out var value))
{
    Console.WriteLine($"Parsed: {value}");
}

// Practical example: Validation with details
public delegate bool TryValidate(string input, out string errorMessage);

TryValidate validateEmail = 
    (string email, out string errorMessage) =>
    {
        if (string.IsNullOrEmpty(email))
        {
            errorMessage = "Email cannot be empty";
            return false;
        }
        if (!email.Contains("@"))
        {
            errorMessage = "Invalid email format";
            return false;
        }
        errorMessage = string.Empty;
        return true;
    };

💡 Key Benefits:

  • Better performance — Avoid copying large structs
  • More expressive — Write functional code with side effects when needed
  • Cleaner APIs — Use lambdas in more scenarios without wrapper methods
  • Type safety — Compiler enforces parameter semantics

7. Partial Events and Constructors

📂 Modular Code Organization

C# 14 extends the partial keyword to events and constructors, allowing you to split their implementation across multiple files. This is especially useful for generated code scenarios and large class implementations.

🧪 Example: Partial Constructors

// File: Person.cs - Main implementation
public partial class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    // Partial constructor declaration
    public partial Person(string firstName, string lastName);
}

// File: Person.Generated.cs - Auto-generated validation logic
public partial class Person
{
    // Partial constructor implementation
    public partial Person(string firstName, string lastName)
    {
        // Generated validation and initialization
        if (string.IsNullOrWhiteSpace(firstName))
            throw new ArgumentException(nameof(firstName));
        
        if (string.IsNullOrWhiteSpace(lastName))
            throw new ArgumentException(nameof(lastName));
        
        FirstName = firstName;
        LastName = lastName;
        Age = 0;
        
        // Call generated initialization logic
        InitializeGeneratedProperties();
    }

    partial void InitializeGeneratedProperties();
}

💡 Example: Partial Events

// File: DataService.cs - Main class
public partial class DataService
{
    // Partial event declaration
    public partial event EventHandler<DataChangedEventArgs> DataChanged;

    public void UpdateData(string newData)
    {
        // Business logic
        ProcessData(newData);
        
        // Raise the partial event
        DataChanged?.Invoke(this, new DataChangedEventArgs(newData));
    }

    private void ProcessData(string data) { /* ... */ }
}

// File: DataService.Logging.cs - Logging concerns
public partial class DataService
{
    // Partial event implementation with logging
    public partial event EventHandler<DataChangedEventArgs> DataChanged
    {
        add
        {
            Console.WriteLine($"Subscriber added to DataChanged event");
            field += value; // C# 14 field keyword
        }
        remove
        {
            Console.WriteLine($"Subscriber removed from DataChanged event");
            field -= value;
        }
    }
}

🎯 Real-World Use Case: Separating Generated Code

// File: ViewModel.cs - Hand-written code
public partial class UserViewModel
{
    private string _username;
    
    // Partial event for property changes
    public partial event PropertyChangedEventHandler PropertyChanged;

    public string Username
    {
        get => _username;
        set
        {
            if (_username != value)
            {
                _username = value;
                OnPropertyChanged(nameof(Username));
            }
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// File: ViewModel.Generated.cs - Source generated code
public partial class UserViewModel
{
    // Generated partial constructor with dependency injection
    public partial UserViewModel(
        ILogger logger,
        IDataService dataService);

    public partial UserViewModel(
        ILogger logger,
        IDataService dataService)
    {
        // Generated initialization
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _dataService = dataService ?? throw new ArgumentNullException(nameof(dataService));
        
        _logger.LogInformation("UserViewModel created");
    }

    // Generated partial event implementation with weak references
    public partial event PropertyChangedEventHandler PropertyChanged
    {
        add { _weakEventManager.AddHandler(value); }
        remove { _weakEventManager.RemoveHandler(value); }
    }

    private readonly WeakEventManager _weakEventManager = new();
    private readonly ILogger _logger;
    private readonly IDataService _dataService;
}

💡 Key Benefits:

  • Clean separation — Keep generated code separate from hand-written code
  • Better maintainability — Organize large classes across multiple files
  • Source generators — Perfect for code generation scenarios
  • Team collaboration — Different team members can work on different aspects

8. User-Defined Compound Assignment Operators

🔧 Customize Compound Operations

C# 14 allows you to define custom behavior for compound assignment operators like +=, -=, *=, etc. This enables more intuitive APIs for custom types.

🧪 Example: Custom Vector Type

public struct Vector3
{
    public double X, Y, Z;

    public Vector3(double x, double y, double z)
    {
        X = x; Y = y; Z = z;
    }

    // Define the fundamental operators
    public static Vector3 operator +(Vector3 a, Vector3 b)
        => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);

    public static Vector3 operator *(Vector3 v, double scalar)
        => new(v.X * scalar, v.Y * scalar, v.Z * scalar);
}

// Usage — Compound assignments use the defined operators
var velocity = new Vector3(1, 2, 3);
var acceleration = new Vector3(0.1, 0.2, 0.3);

velocity += acceleration; // Uses operator + under the hood
velocity *= 0.5;          // Uses operator * under the hood

💡 Example: StringBuilder-like Builder Pattern

public class QueryBuilder
{
    private StringBuilder _query = new();

    // Define fundamental operators
    public static QueryBuilder operator +(QueryBuilder builder, string clause)
    {
        if (builder._query.Length > 0)
            builder._query.Append(" ");
        builder._query.Append(clause);
        return builder;
    }

    public static QueryBuilder operator &(QueryBuilder builder, string condition)
    {
        if (!builder._query.ToString().Contains("WHERE"))
            builder._query.Append(" WHERE ");
        else
            builder._query.Append(" AND ");
        builder._query.Append(condition);
        return builder;
    }

    public override string ToString() => _query.ToString();
}

// Usage — Intuitive query building
var query = new QueryBuilder();
query += "SELECT * FROM Users";
query &= "Age > 18";
query &= "IsActive = 1";
query += "ORDER BY Name";

Console.WriteLine(query);
// Output: SELECT * FROM Users WHERE Age > 18 AND IsActive = 1 ORDER BY Name

🎯 Example: Collection Builder with Validation

public class ValidatedCollection<T>
{
    private List<T> _items = new();
    private Func<T, bool> _validator;

    public ValidatedCollection(Func<T, bool> validator)
    {
        _validator = validator;
    }

    // Define fundamental operators
    public static ValidatedCollection<T> operator +(
        ValidatedCollection<T> collection, T item)
    {
        if (collection._validator(item))
        {
            collection._items.Add(item);
            Console.WriteLine($"✓ Added: {item}");
        }
        else
        {
            Console.WriteLine($"✗ Rejected: {item}");
        }
        return collection;
    }

    public static ValidatedCollection<T> operator -(
        ValidatedCollection<T> collection, T item)
    {
        collection._items.Remove(item);
        return collection;
    }

    public int Count => _items.Count;
}

// Usage
var numbers = new ValidatedCollection<int>(x => x > 0);
numbers += 5;   // ✓ Added: 5
numbers += -3;  // ✗ Rejected: -3
numbers += 10;  // ✓ Added: 10

💡 Key Benefits:

  • Intuitive syntax — Make custom types behave like built-in types
  • Performance — Optimize in-place operations without temporary allocations
  • Fluent APIs — Create chainable, readable code
  • Domain modeling — Express domain concepts naturally

9. Improved Interpolated Strings

📝 Enhanced String Formatting

C# 14 brings significant improvements to string interpolation, including better performance, enhanced formatting options, and support for custom interpolated string handlers.

🧪 Example: Advanced Formatting

// Enhanced format specifiers
var price = 1234.567;
var date = new DateTime(2025, 11, 3);

// Multiple format specifiers with alignment
var formatted = $"Price: {price,15:C2} | Date: {date:yyyy-MM-dd}";
// Output: "Price:      $1,234.57 | Date: 2025-11-03"

// C# 14: Conditional formatting within interpolation
var stock = 5;
var message = $"Stock: {stock} ({(stock > 0 ? \"Available\" : \"Out of stock\")})";

// Nested interpolations (improved in C# 14)
var items = new[] { "Apple", "Banana", "Cherry" };
var list = $"Items: {string.Join(", ", items.Select(x => $"'{x}'"))}";
// Output: "Items: 'Apple', 'Banana', 'Cherry'"

💡 Performance: Custom Interpolated String Handlers

// Custom handler for SQL query building with parameter safety
[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
public ref struct SqlInterpolatedStringHandler
{
    private System.Text.StringBuilder _builder;
    private System.Collections.Generic.List<object> _parameters;

    public SqlInterpolatedStringHandler(int literalLength, int formattedCount)
    {
    _builder = new System.Text.StringBuilder(literalLength);
    _parameters = new System.Collections.Generic.List<object>(formattedCount);
    }

    public void AppendLiteral(string s) => _builder.Append(s);

    public void AppendFormatted<T>(T value)
    {
        _parameters.Add(value);
        _builder.Append($"@p{_parameters.Count - 1}");
    }

    public (string Query, object[] Parameters) GetResult()
        => (_builder.ToString(), _parameters.ToArray());
}

// Usage - Safe parameterized queries
public static class SqlQuery
{
    public static (string Query, object[] Parameters) Create(
        SqlInterpolatedStringHandler handler)
    {
        return handler.GetResult();
    }
}

// Create safe SQL queries
var userId = 42;
var userName = "John";

var (query, parameters) = SqlQuery.Create(
    $"SELECT * FROM Users WHERE Id = {userId} AND Name = {userName}");

Console.WriteLine(query);
// Output: "SELECT * FROM Users WHERE Id = @p0 AND Name = @p1"
Console.WriteLine(string.Join(", ", parameters));
// Output: "42, John"

🎯 Example: Logging with Conditional Evaluation

// Custom logger that only evaluates expensive operations if needed
public enum LogLevel { Debug = 1, Info = 2, Warn = 3, Error = 4 }

public class SmartLogger
{
    private LogLevel _currentLevel = LogLevel.Info;

    public void Log(
        LogLevel level,
        [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("this", "level")]
        LogInterpolatedStringHandler handler)
    {
        if (level >= _currentLevel)
        {
            Console.WriteLine(handler.ToString());
        }
    }
}

[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
public ref struct LogInterpolatedStringHandler
{
    private System.Text.StringBuilder? _builder;
    private bool _enabled;

    public LogInterpolatedStringHandler(
        int literalLength,
        int formattedCount,
        SmartLogger logger,
        LogLevel level,
        out bool isEnabled)
    {
        _enabled = level >= logger._currentLevel;
        isEnabled = _enabled;
        _builder = _enabled ? new System.Text.StringBuilder(literalLength) : null;
    }

    public void AppendLiteral(string s)
    {
        if (_enabled) _builder!.Append(s);
    }

    public void AppendFormatted<T>(T value)
    {
        if (_enabled) _builder!.Append(value);
    }

    public override string ToString() => _builder?.ToString() ?? string.Empty;
}

// Usage - Expensive operations only run if logging is enabled
var logger = new SmartLogger();
var data = new List<int> { 1, 2, 3 };

string ExpensiveOperation(IEnumerable<int> items)
{
    return string.Join(",", items.Select(i => (i * i).ToString()));
}

// This expensive operation only runs if Debug level is enabled
logger.Log(LogLevel.Debug,
    $"Processing {data.Count} items, details: {ExpensiveOperation(data)}");

📊 Real-World Example: URL Builder

public class UrlBuilder
{
    public static string Build(UrlInterpolatedStringHandler handler)
        => handler.ToString();
}

[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
public ref struct UrlInterpolatedStringHandler
{
    private System.Text.StringBuilder _builder;

    public UrlInterpolatedStringHandler(int literalLength, int formattedCount)
    {
    _builder = new System.Text.StringBuilder(literalLength);
    }

    public void AppendLiteral(string s) => _builder.Append(s);

    public void AppendFormatted(string value)
    {
        // Automatically URL-encode interpolated values
        _builder.Append(Uri.EscapeDataString(value ?? string.Empty));
    }

    public override string ToString() => _builder.ToString();
}

// Usage - Automatic URL encoding
var searchTerm = "C# programming";
var category = "Books & Media";

var url = UrlBuilder.Build(
    $"https://example.com/search?q={searchTerm}&category={category}");

Console.WriteLine(url);
// Output: "https://example.com/search?q=C%23%20programming&category=Books%20%26%20Media"

💡 Key Benefits:

  • Zero-allocation — Custom handlers can optimize memory usage
  • Conditional evaluation — Skip expensive operations when not needed
  • Type safety — Enforce formatting rules at compile time
  • Domain-specific — Create specialized string builders (SQL, URLs, etc.)
  • Better performance — Reduced allocations in hot paths

10. Collection Expressions (Preview)

📦 Concise Collection Initialization

C# 14 introduces collection expressions, providing a unified, concise syntax for creating and initializing collections of any type. This feature simplifies code and makes collection creation more intuitive.

🧪 Example: Basic Collection Expressions

// Traditional way
var oldList = new List<int> { 1, 2, 3, 4, 5 };
var oldArray = new int[] { 1, 2, 3, 4, 5 };

// C# 14: Collection expressions - unified syntax
List<int> list = [1, 2, 3, 4, 5];
int[] array = [1, 2, 3, 4, 5];
Span<int> span = [1, 2, 3, 4, 5];

// Empty collections
List<string> empty = [];
int[] emptyArray = [];

💡 Example: Spread Operator

// Combine collections with spread operator ..
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
int[] third = [7, 8, 9];

// Spread and combine
int[] combined = [..first, ..second, ..third];
// Result: [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Mix literals and spreads
int[] mixed = [0, ..first, 10, ..second, 20];
// Result: [0, 1, 2, 3, 10, 4, 5, 6, 20]

// Spread with LINQ
var numbers = [1, 2, 3, 4, 5];
var doubled = [..numbers.Select(x => x * 2)];
// Result: [2, 4, 6, 8, 10]

🎯 Example: Pattern Matching with Collections

// Pattern matching with collection expressions
string DescribeCollection(int[] numbers) => numbers switch
{
    [] => "Empty collection",
    [var single] => $"Single element: {single}",
    [var first, var second] => $"Two elements: {first}, {second}",
    [var first, .., var last] => $"First: {first}, Last: {last}",
    _ => $"Collection with {numbers.Length} elements"
};

// Usage
Console.WriteLine(DescribeCollection([]));           // "Empty collection"
Console.WriteLine(DescribeCollection([42]));         // "Single element: 42"
Console.WriteLine(DescribeCollection([1, 2]));       // "Two elements: 1, 2"
Console.WriteLine(DescribeCollection([1, 2, 3, 4])); // "First: 1, Last: 4"

🔥 Real-World Example: API Response Building

public record ApiResponse(string Status, object[] Data);

public class UserService
{
    public ApiResponse GetUsers(bool includeAdmin, bool includeGuests)
    {
        var regularUsers = GetRegularUsers();
        var adminUsers = includeAdmin ? GetAdminUsers() : [];
        var guestUsers = includeGuests ? GetGuestUsers() : [];

        // Build response with collection expressions
        return new ApiResponse(
            "success",
            [
                ..regularUsers,
                ..adminUsers,
                ..guestUsers
            ]
        );
    }

    private object[] GetRegularUsers() => [
        new { Id = 1, Name = "Alice", Role = "User" },
        new { Id = 2, Name = "Bob", Role = "User" }
    ];

    private object[] GetAdminUsers() => [
        new { Id = 100, Name = "Admin", Role = "Admin" }
    ];

    private object[] GetGuestUsers() => [
        new { Id = 200, Name = "Guest", Role = "Guest" }
    ];
}

📊 Example: Building Test Data

// Easy test data creation
public class Product
{
    public string Name { get; set; } = string.Empty;
    public int Price { get; set; }
}

public static class TestDataBuilder
{
    public static List<Product> CreateTestProducts()
    {
        var electronics = CreateElectronics();
        var books = CreateBooks();
        var featured = new Product { Name = "Featured Item", Price = 999 };

        // Combine with collection expressions
        return [featured, ..electronics, ..books];
    }

    private static Product[] CreateElectronics() => [
        new Product { Name = "Laptop", Price = 1200 },
        new Product { Name = "Phone", Price = 800 }
    ];

    private static Product[] CreateBooks() => [
        new Product { Name = "C# Guide", Price = 45 },
        new Product { Name = "Design Patterns", Price = 55 }
    ];
}

// Simple check without external test frameworks
int Sum(int[] numbers) => numbers.Sum();
Console.WriteLine(Sum([1,2,3]));   // 6
Console.WriteLine(Sum([10,20,30])); // 60

Performance Example: Stack Allocation

// Collection expressions work with Span for stack allocation
public static void ProcessData()
{
    // Stack-allocated span with collection expression
    Span<int> numbers = [1, 2, 3, 4, 5];
    
    // Zero heap allocation!
    foreach (var num in numbers)
    {
        Console.WriteLine(num * 2);
    }

    // Combine with stackalloc for larger data
    Span<byte> buffer = stackalloc byte[256];
    Span<byte> header = [0xFF, 0xFE, 0xFD];
    
    // Copy header to buffer
    header.CopyTo(buffer);
}

💡 Key Benefits:

  • Unified syntax — Same syntax for arrays, lists, spans, and more
  • More readable — Cleaner, less verbose collection initialization
  • Spread operator — Easy collection combination and manipulation
  • Pattern matching — Powerful collection destructuring
  • Performance — Works with stack-allocated spans
  • Type inference — Compiler figures out the target type

🚀 Conclusion

C# 14 continues the language's tradition of thoughtful, developer-friendly improvements. From extension members that extend types more naturally, to the field keyword that eliminates boilerplate, these features help you write code that's both more expressive and maintainable.

The addition of lambda parameter modifiers enables more efficient functional programming patterns, while partial events and constructors improve code organization in large projects. User-defined compound assignment operators make custom types feel more natural, and improved interpolated strings provide both performance and flexibility.

Finally, collection expressions bring a modern, unified syntax for working with collections, making code more concise and readable. As you adopt C# 14 in your projects, you'll find these small enhancements add up to significant productivity gains. The language continues to evolve in ways that respect existing code while opening new possibilities for elegant solutions.

📚 Next Steps:

  • Experiment with these features in your own projects
  • Explore the official C# 14 documentation for more details
  • Share your experiences and patterns with the community
  • Stay tuned for more advanced use cases and best practices

Happy coding with C# 14! 🎉

What's New in .NET 10?

What's New in .NET 10? What's New in .NET 10? .NET 10 brings gr...