DEV Community

Cover image for C# Keeps Getting Better: My Favourite Modern Language Features
Mo
Mo

Posted on

C# Keeps Getting Better: My Favourite Modern Language Features

If you've been coding in C# for a while, you know it's a language that refuses to sit still. Every new version brings a fresh set of tools, and honestly, as someone who's been wrangling .NET for years, it's pretty exciting to see how much more expressive, concise, and just plain fun C# has become. Gone are the days of endless boilerplate for simple tasks!

Let's dive into some of the features that have genuinely changed how I approach C# development.

Streamlining Your Code: Less Ceremony, More Action!

One of the biggest shifts in recent C# versions has been a strong push towards reducing boilerplate. We're all busy, right? Why type more when you can say the same thing with less?

Top-Level Statements: Kickstarting Your Apps

Remember when even a simple "Hello World" console app needed a namespace, a class, and a static void Main(string[] args)? It felt a bit heavy for quick scripts or learning examples. Well, say hello to top-level statements!

Now, your entire program can look like this:

// Program.cs
Console.WriteLine("Hello, modern C#!");

// You can even use async/await directly
await Task.Delay(100);
Console.WriteLine("Waiting is over!");
Enter fullscreen mode Exit fullscreen mode

This is fantastic for small utilities, Azure Functions, or just getting an idea off the ground without the cognitive load of a full class structure. It makes C# feel much more scripting-friendly.

Global Using Directives: De-cluttering Your Files

How many times have you scrolled past a dozen using statements at the top of every file? System, System.Collections.Generic, System.Linq, Microsoft.EntityFrameworkCore... the list goes on.

global using directives let you declare these common using statements once for your entire project. Just drop them into a file (often GlobalUsings.cs or Usings.cs), and they're available everywhere.

// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using Microsoft.EntityFrameworkCore;
// ...and so on
Enter fullscreen mode Exit fullscreen mode

The difference this makes in larger projects is immense. Your individual files become cleaner, focusing purely on the code within. No more endless scrolling past using blocks!

GIF

Embracing Immutability: Records & init Setters

Immutability is a huge win for robust, predictable software. When an object's state can't change after it's created, you eliminate entire classes of bugs related to unexpected side effects. C# has made it a first-class citizen with records and init setters.

Record Types: The Data-First Class

Records are essentially classes (or structs) designed for immutable data models. They come with a ton of goodies out of the box, like value-based equality, concise syntax, and non-destructive mutation (with expressions).

Imagine you're defining a Data Transfer Object (DTO) or an entity:

// Old way (class) - lots of boilerplate for immutability, equality, and ToString
public class Product
{
    public int Id { get; }
    public string Name { get; }
    public decimal Price { get; }

    public Product(int id, string name, decimal price)
    {
        Id = id;
        Name = name;
        Price = price;
    }
    // Need to override Equals, GetHashCode, ToString manually...
}

// New way (record) - concise, immutable by default, value equality
public record Product(int Id, string Name, decimal Price);
Enter fullscreen mode Exit fullscreen mode

That's right, a single line! Records are perfect for scenarios where you just need to represent data. They save you from writing mountains of boilerplate for Equals, GetHashCode, and ToString, and they promote a functional style of programming.

Technical Diagram Placeholder

init Setters: Immutable Properties, Flexible Creation

Even with regular classes, init setters provide a powerful way to ensure properties can only be set during object initialisation, enforcing immutability after creation.

public class UserProfile
{
    public int Id { get; init; } // Can only be set during object creation
    public string Username { get; init; }
    public DateTime RegisteredDate { get; init; } = DateTime.UtcNow; // Default value also works

    // Usage
    var user = new UserProfile { Id = 1, Username = "devfriend" };
    // user.Id = 2; // Error: Init-only setter cannot be used here
}
Enter fullscreen mode Exit fullscreen mode

This gives you the best of both worlds: object initialiser syntax for easy construction, and immutability once the object is fully formed. Super handy for configuration objects or read-only view models.

Smarter Decisions: Enhanced Pattern Matching

Pattern matching isn't new, but it's been steadily enhanced, making complex conditional logic incredibly readable and concise. It allows you to test an expression against various "patterns" and execute code based on a match.

switch Expressions and Property Patterns

Gone are the days of clunky switch statements with case and break. switch expressions are, well, expressions – they return a value, making them perfect for transformations. Combine that with property patterns, and you have some serious power.

// Imagine a DTO or some input object
public record OrderItem(string ItemType, int Quantity, decimal UnitPrice);

public decimal CalculateDiscount(OrderItem item) => item switch
{
    // If ItemType is "Book" AND Quantity > 5, apply 10% discount
    { ItemType: "Book", Quantity: > 5 } => item.Quantity * item.UnitPrice * 0.10M,
    // If ItemType is "Software" AND UnitPrice > 100, apply 5% discount
    { ItemType: "Software", UnitPrice: > 100 } => item.Quantity * item.UnitPrice * 0.05M,
    // Any other case
    _ => 0M
};

// Even simpler: type patterns
public string GetShapeInfo(object shape) => shape switch
{
    Circle c when c.Radius > 10 => $"Large Circle with radius {c.Radius}",
    Circle c => $"Small Circle with radius {c.Radius}",
    Rectangle r => $"Rectangle {r.Width}x{r.Height}",
    _ => "Unknown shape"
};
Enter fullscreen mode Exit fullscreen mode

This syntax is incredibly powerful for expressing intent clearly. Instead of nested if/else if statements, you get a clean, declarative structure that's easy to read and maintain.

Wrapping Up

These are just a few of my personal highlights from the recent evolutions of C#. From making quick scripts easier with top-level statements to building robust, immutable data models with records, and writing more expressive conditional logic with pattern matching, C# continues to empower developers to write cleaner, more maintainable code.

It's a fantastic time to be a C# developer, and I'm always excited to see what's next!

What new C# feature has made the biggest difference in your daily coding life? Let me know in the comments!

Top comments (0)