C# continues to evolve with every new version, offering developers modern tools and features to improve productivity, maintainability, and performance. C# 12, the latest release as part of the .NET 8 ecosystem, introduces several new features that simplify code, enhance performance, and bring more flexibility to the language. From primary constructors to collection expressions, C# 12 is packed with enhancements that will help you write cleaner and more efficient code.
In this guide, we’ll explore the new features in C# 12, with practical examples and use cases to help you integrate them into your projects.
Table of Contents
- Primary Constructors for All Types
- Collection Expressions
- Default Values for Lambda Parameters
- Interceptors in C# 12
- Improved
switch
Expressions - Enhanced
using
Directives - Conclusion
1. Primary Constructors for All Types
In previous versions of C#, primary constructors were only available for record types. C# 12 extends this feature to all classes and structs, making it easier to define constructors without writing redundant code. A primary constructor allows you to declare parameters directly in the type definition, reducing boilerplate code and improving readability.
How to Use Primary Constructors
You can now define a constructor for any class or struct directly in the type declaration:
public class Person(string name, int age) { public string Name { get; } = name; public int Age { get; } = age; public void PrintInfo() { Console.WriteLine($"Name: {Name}, Age: {Age}"); } }
Example
var person = new Person("Alice", 30); person.PrintInfo(); // Output: Name: Alice, Age: 30
In this example, the Person
class has a primary constructor that automatically assigns the name
and age
parameters to properties. This eliminates the need for a separate constructor, making the code more concise and easier to read.
When to use primary constructors:
- When you need a simple constructor that directly initializes properties from parameters.
- In data-centric classes where the constructor just assigns values to fields or properties.
2. Collection Expressions
Collection expressions are a powerful new feature in C# 12 that simplifies working with collections by enabling concise and readable code when constructing lists, arrays, or other collection types. This is especially useful for quickly building complex data structures in a declarative manner.
How to Use Collection Expressions
Collection expressions allow you to construct collections directly without having to rely on multiple method calls or intermediate variables.
var numbers = [1, 2, 3, 4, 5];
You can also use collection expressions for more complex cases, such as generating a list based on a range of values or applying a transformation:
var squares = [for (var i = 1; i <= 5; i++) i * i];
Example
var evenNumbers = [for (var i = 0; i <= 10; i++) if (i % 2 == 0) i]; Console.WriteLine(string.Join(", ", evenNumbers)); // Output: 0, 2, 4, 6, 8, 10
In this example, the collection expression generates a list of even numbers between 0 and 10, and the if
clause is used to filter the values.
When to use collection expressions:
- When you want to quickly construct a list or array with minimal syntax.
- When working with data transformations or filtering collections.
3. Default Values for Lambda Parameters
C# 12 introduces default values for lambda parameters, allowing developers to provide default values for parameters in lambda expressions. This feature simplifies code by reducing the need to overload lambda expressions or create multiple versions with different parameter signatures.
How to Use Default Values in Lambda Parameters
You can now specify default values directly in the lambda declaration, just like with regular methods:
Func<int, int, int> add = (x = 1, y = 2) => x + y; Console.WriteLine(add()); // Output: 3 Console.WriteLine(add(5)); // Output: 7 Console.WriteLine(add(5, 10)); // Output: 15
Example
Func<string, string, string> greet = (name = "Guest", message = "Hello") => $"{message}, {name}!"; Console.WriteLine(greet()); // Output: Hello, Guest! Console.WriteLine(greet("Alice")); // Output: Hello, Alice! Console.WriteLine(greet("Alice", "Welcome")); // Output: Welcome, Alice!
In this example, the greet
lambda expression has two parameters with default values. If no arguments are provided, the default values are used.
When to use default lambda parameters:
- When you want to simplify lambdas that have optional parameters.
- When overloading lambdas or creating multiple versions of a function feels excessive.
4. Interceptors in C# 12
Interceptors in C# 12 allow you to define methods that can intercept calls to other methods or properties. This feature is particularly useful for implementing cross-cutting concerns like logging, caching, or validation, without modifying the original method logic.
How to Use Interceptors
Interceptors can be defined in the class and can wrap method calls with additional behavior before or after the method is executed:
public class Service { public virtual void DoWork() => Console.WriteLine("Doing work..."); [Intercept] public void OnBeforeDoWork() { Console.WriteLine("Before doing work"); } }
Example
var service = new Service(); service.DoWork(); // Output: // Before doing work // Doing work...
In this example, the OnBeforeDoWork
method is invoked before the DoWork
method, allowing the DoWork
logic to remain unchanged while still applying additional functionality.
When to use interceptors:
- When you want to add cross-cutting concerns such as logging or security without modifying the business logic.
- When you need to introduce hooks before or after method execution.
5. Improved switch
Expressions
C# 12 builds on the power of switch
expressions by adding more flexibility and capabilities. It introduces new pattern matching capabilities, allowing you to write more expressive and concise switch expressions.
New Features in switch
Expressions
You can now use new pattern matching constructs to create more complex and flexible switch
expressions:
int score = 85; string grade = score switch { >= 90 => "A", >= 80 and < 90 => "B", >= 70 and < 80 => "C", _ => "F" };
Example
string weather = "sunny"; string activity = weather switch { "sunny" => "Go for a walk", "rainy" => "Stay indoors", "snowy" => "Build a snowman", _ => "Relax at home" }; Console.WriteLine(activity); // Output: Go for a walk
In this example, the switch
expression evaluates the weather
variable and assigns an activity based on its value.
When to use improved switch
expressions:
- When handling complex conditional logic in a clean and concise manner.
- When working with pattern matching and want to avoid verbose
if-else
blocks.
6. Enhanced using
Directives
C# 12 brings further enhancements to using
directives, making it easier to manage dependencies and external resources in your codebase. It now supports better scoping and organization of using
statements, particularly for larger projects.
Global Using in Namespaces
In addition to C# 10’s global using
directives, C# 12 now allows you to group using
statements within namespaces, improving code organization:
namespace MyApp { using System; using System.Collections.Generic; class Program { // Code here can access the above using directives } }
This feature ensures that using
directives are scoped to specific namespaces, keeping the codebase clean and reducing the chance of conflicts.
Example
namespace FinanceApp { using System; using System.Linq; class BudgetCalculator { // Can use System and LINQ without global scope } }
When to use enhanced using
directives:
- When you need better control over
using
directives in large projects. - To avoid namespace conflicts and improve code readability.
7. Conclusion
C# 12 brings an array of features that enhance productivity and streamline development. With primary constructors, you can simplify object initialization, while collection expressions and default values for lambda parameters make working with data more intuitive and concise. Interceptors introduce a new way to implement cross-cutting concerns without changing existing logic, and improved switch
expressions add even more power to pattern matching.
By incorporating these new features into your C# 12 projects, you’ll be able to write cleaner, more efficient, and more maintainable code. Whether you’re building enterprise-level applications, cloud services, or desktop software, mastering these features will help you stay ahead in the rapidly evolving .NET ecosystem.