Skip to content

C#'s Hidden Gems and Modern Features

Published: at 07:19 PM

C#‘s Hidden Gems and Modern Features

C# has evolved significantly over the years, introducing many powerful features that can significantly improve your code quality and productivity. Let’s explore the most valuable but often overlooked features.

Modern Pattern Matching

public static string GetShapeDescription(object shape)
{
    return shape switch
    {
        Circle c => $"Circle with radius {c.Radius}",
        Rectangle r when r.Width == r.Height => $"Square with side {r.Width}",
        Rectangle r => $"Rectangle {r.Width}x{r.Height}",
        Line l => $"Line with length {l.Length}",
        _ => "Unknown shape"
    };
}

Pattern matching provides a powerful way to handle different types and conditions in a clean, type-safe manner. The switch expression syntax makes the code more readable and maintainable than traditional if-else chains.

Traditional if-else Approach

public static string GetShapeDescription(object shape)
{
    if (shape is Circle c)
    {
        return $"Circle with radius {c.Radius}";
    }
    else if (shape is Rectangle r && r.Width == r.Height)
    {
        return $"Square with side {r.Width}";
    }
    else if (shape is Rectangle r)
    {
        return $"Rectangle {r.Width}x{r.Height}";
    }
    else if (shape is Line l)
    {
        return $"Line with length {l.Length}";
    }
    return "Unknown shape";
}

Modern Records with With Expressions

public record Person(string FirstName, string LastName, int Age);

var person = new Person("John", "Doe", 30);
var updated = person with { LastName = "Smith" };

// Deconstruct and use
var (firstName, lastName, age) = updated;

Records provide a concise way to create immutable reference types with value-based equality. The with expression makes updating immutable data straightforward and expressive.

Traditional Class Approach

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public Person(string firstName, string lastName, int age)
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
    }

    public Person UpdateLastName(string newLastName)
    {
        return new Person(FirstName, newLastName, Age);
    }
}

The traditional class approach requires more boilerplate code and manual implementation of immutability patterns. It’s more error-prone and harder to maintain than records.

Modern Top-Level Program

// File: program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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

Top-level programs eliminate the need for explicit Main methods and Program classes, making the code more concise and easier to understand.

Traditional Program Structure

// File: Program.cs
namespace MyApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var app = builder.Build();

            app.MapGet("/", () => "Hello World!");
            app.Run();
        }
    }
}

The traditional approach requires more code structure, including explicit namespace, class, and Main method declarations. While functional, it’s more verbose than necessary.## Global Using Directives

Modern Global Using Directives

// File: GlobalUsings.cs
global using Microsoft.AspNetCore.Builder;
global using Microsoft.Extensions.DependencyInjection;

// File: Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

Traditional Using Directives

// File: Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

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

Traditional using directives require repeating namespace imports in each file, leading to code duplication and maintenance challenges as projects grow.


Previous Post
Managing Orphaned Upload Files
Next Post
Labeling Symlinks in Windows