1. Global Usings
Global usings allow you to define using
directives once for the entire project, rather than repeating them in every file. This simplifies code management and reduces redundancy, especially for commonly used namespaces.
Example: Global Usings
- GlobalUsings.cs
// This file can be placed in your project, e.g., GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Threading.Tasks;
- Program.cs
class Program
{
static void Main()
{
List<int> numbers = new() { 1, 2, 3 };
Console.WriteLine(string.Join(", ", numbers));
}
}
- Explanation: By declaring global usings in
GlobalUsings.cs
, we don't need to writeusing System;
orusing System.Collections.Generic;
in every file. These namespaces are automatically available throughout the project.
2. File-Scoped Namespaces
File-scoped namespaces simplify the syntax for declaring namespaces, especially for single-file classes. Instead of using the traditional block-scoped syntax, you can declare a file-scoped namespace with just one line.
Example: File-Scoped Namespaces
namespace MyApp;
class Program
{
static void Main()
{
Console.WriteLine("File-Scoped Namespace Example");
}
}
- Explanation: The
namespace MyApp;
declaration applies to the entire file without requiring an extra pair of braces ({}
). This is useful for keeping the code cleaner in files that only have a single namespace.
3. Record Structs
Introduced in C# 10, record structs are a combination of records and structs, allowing you to define value-type records that maintain immutability and value-based equality, but without the overhead of reference types.
Example: Record Structs
public record struct Point(int X, int Y);
class Program
{
static void Main()
{
var point1 = new Point(3, 4);
var point2 = new Point(3, 4);
Console.WriteLine(point1 == point2); // Output: True (value-based equality)
Console.WriteLine(point1); // Output: Point { X = 3, Y = 4 }
}
}
- Explanation:
Point
is a value-type record (struct). It benefits from the immutability, value-based equality, and concise syntax of records, but it behaves like a struct, meaning it's stored on the stack rather than the heap.
4. Lambda Improvements
Lambda expressions have been improved in C# 10, allowing them to be used in more places, including return types, and allowing more complex scenarios like natural type inference and attributes on lambdas.
Example: Lambda Improvements
class Program
{
static Func<int, int, int> GetAdder()
{
return (x, y) => x + y; // Returning a lambda
}
static void Main()
{
var adder = GetAdder();
Console.WriteLine(adder(3, 4)); // Output: 7
}
}
- Explanation: The
GetAdder
method returns a lambda expression, demonstrating how lambdas can be used as return types. In C# 10, you can now also apply attributes and use more advanced types with lambdas.
5. Interpolated String Handlers
Interpolated string handlers, introduced in C# 10, optimize string interpolation for better performance by allowing you to control how interpolated strings are constructed. This can reduce allocations and improve performance, particularly in performance-critical code.
Example: Interpolated String Handlers
using System.Runtime.CompilerServices;
class Logger
{
public void Log(string message)
{
Console.WriteLine(message);
}
public void LogInterpolated([InterpolatedStringHandlerArgument("")] ref DefaultInterpolatedStringHandler handler)
{
Console.WriteLine(handler.ToString());
}
}
class Program
{
static void Main()
{
Logger logger = new();
logger.LogInterpolated($"Logging an interpolated string with value {42}.");
}
}
- Explanation: The
LogInterpolated
method takes advantage of theInterpolatedStringHandler
to optimize string construction, potentially avoiding unnecessary allocations depending on the string's content. This is especially useful in high-performance scenarios like logging.
6. Enhanced Async/Parallel Programming
.NET has continued to improve task-based asynchronous programming, providing better performance and APIs that make parallel and asynchronous work easier to handle.
Example: Enhanced Async/Parallel Programming
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var task1 = Task.Run(() => DoWork("Task 1"));
var task2 = Task.Run(() => DoWork("Task 2"));
await Task.WhenAll(task1, task2);
}
static async Task DoWork(string taskName)
{
await Task.Delay(1000);
Console.WriteLine($"{taskName} completed.");
}
}
- Explanation: This example demonstrates using
Task.WhenAll
to run multiple asynchronous tasks in parallel and wait for all of them to complete. The .NET runtime optimizes task handling, improving performance and scalability for asynchronous and parallel code.
Summary
- Global Usings: Declares common namespaces once globally, reducing redundancy and simplifying project-wide code management.
- File-Scoped Namespaces: Simplifies namespace declaration for single-file classes by using a single-line declaration.
- Record Structs: Combines the immutability and value-based equality of records with the performance benefits of structs (value types).
- Lambda Improvements: Enhances lambda expressions, allowing them to be used in more places, such as return types, and supports more complex scenarios.
- Interpolated String Handlers: Optimizes string interpolation for performance, reducing allocations and improving efficiency in performance-critical code.
- Enhanced Async/Parallel Programming: Provides better task-based APIs and asynchronous performance, making parallel and asynchronous programming easier and more efficient.