What's New In C# 11?

C# 11, introduced with .NET 7, brings a plethora of new features and enhancements designed to simplify development, improve performance, and enhance the expressiveness of the language. In this blog post, we’ll delve into some of the advanced features of C# 11, illustrating their usage with real-world examples.

Table of Contents

  1. Raw String Literals
  2. List Patterns
  3. Required Members
  4. Generic Attributes
  5. File-Scoped Types
  6. UTF-8 String Literals
  7. Extended nameof Scope
  8. Improved Interpolated Strings
  9. Enhanced Lambda Expressions
  10. Static Abstract Members in Interfaces

1. Raw String Literals

Raw string literals simplify working with multi-line strings and strings containing special characters, such as JSON, XML, and code snippets.

Example: JSON Configuration

Before C# 11:


var jsonString = "{\n  \"name\": \"John\",\n  \"age\": 30,\n  \"city\": \"New York\"\n}";

With C# 11:


var jsonString = """
{
    "name": "John",
    "age": 30,
    "city": "New York"
}
""";

This feature makes the code more readable and easier to maintain, especially for large blocks of text.

2. List Patterns

List patterns allow you to match and destructure arrays and lists, enhancing pattern matching capabilities.

Example: Checking Array Pattern


int[] numbers = { 1, 2, 3, 4 };

if (numbers is [1, 2, 3, 4])
{
    Console.WriteLine("The array matches the pattern!");
}

if (numbers is [_, 2, ..])
{
    Console.WriteLine("The second element is 2, followed by any number of elements.");
}

This is particularly useful in scenarios where you need to validate or extract data from arrays or lists based on specific patterns.

3. Required Members

The required keyword enforces that certain properties must be initialized during object creation.

Example: User Registration

public class User
{
    public required string Username { get; set; }
    public required string Password { get; set; }
    public string Email { get; set; }
}
// Usage
var user = new User { Username = "johndoe", Password = "securepassword" };

This ensures that essential properties are always provided, reducing the risk of runtime errors.

4. Generic Attributes

Generic attributes add flexibility and reduce boilerplate code, allowing for more reusable components.

Example: Logging Attribute


public class LogAttribute<T> : Attribute { }

[LogAttribute<int>]
public class MyClass { }

This feature is beneficial in scenarios like aspect-oriented programming, where attributes are used to implement cross-cutting concerns such as logging or validation.

5. File-Scoped Types

File-scoped types restrict the accessibility of types to the file in which they are declared, enhancing encapsulation.

Consider a scenario where we have helper classes that should not be exposed outside their respective files.

Example: Helper Class

Helper.cs


file class Helper
    {
        public void Print(string message)
        {
            Console.WriteLine(message);
        }
    }

    public class Logger
    {
        public void Log(string message)
        {
            Helper helper = new Helper();
            helper.Print(message);
        }
    }

Helper2.cs


file class Helper
    {
        public void Print(string message)
        {
            Console.WriteLine(message);
        }
    }

    public class LoggerV1
    {
        public void Log(string message)
        {
            Helper helper = new Helper();
            helper.Print(message);
        }
    }

Explanation of the Example

  1. Encapsulation:

    • The Helper class in each file is declared with the file keyword. This restricts the Helper class to be used only within the respective file (Helper.cs or Helper2.cs).
  2. Avoiding Naming Collisions:

    • Both Helper.cs and Helper2.cs define a Helper class. Due to the file keyword, these two classes do not conflict with each other, even though they share the same name. This is particularly beneficial for large projects or when using source generators.
  3. Code Usage:

    • The Logger and LoggerV1 classes can use their respective Helper classes without worrying about conflicts or exposing the Helper implementation outside their files.

6. UTF-8 String Literals

UTF-8 string literals optimize performance and memory usage when working extensively with UTF-8 encoded text.

Example: Working with UTF-8 Text


var utf8String = "This is a UTF-8 string"u8;

This feature is particularly beneficial for applications that process large amounts of text, such as web servers or text analytics tools.

7. Extended nameof Scope

The nameof operator now supports method arguments and type parameters, enhancing its usability.

Example: Debugging


void PrintName<T>(T value)
{
    Console.WriteLine(nameof(value));  
    Console.WriteLine(nameof(T));     
}

This feature is useful for logging and debugging, where you need to refer to method parameters and type parameters dynamically.

8. Improved Interpolated Strings

Interpolated strings are more powerful and efficient, making them suitable for complex string formatting tasks.C# 11 introduces the ability to use interpolated strings as constants, provided all embedded expressions are themselves constant expressions. This feature allows you to use interpolated strings in places that require compile-time constants.

Example: Constant Interpolated Strings

const string appName = "MyApp";
const string greeting = $"Welcome to {appName}!";
Console.WriteLine(greeting); // Output: Welcome to MyApp!

This feature simplifies the creation of complex strings, improving code readability and maintainability.

9. Enhanced Lambda Expressions

C# 11 improves lambda expressions with better type inference and the ability to specify return types explicitly. This makes lambdas more flexible and easier to use.

Example: Explicit Return Type in Lambda

Previously, lambdas had to rely on type inference, which could sometimes be limiting. With C# 11, you can explicitly specify the return type.

Func<int, int, int> add = (int a, int b) => a + b;
Console.WriteLine(add(3, 4)); 

// Using explicit return type
var multiply = (int a, int b) => { return a * b; };
Console.WriteLine(multiply(3, 4));

11. Static Abstract Members in Interfaces

Static abstract members in interfaces enable more flexible and powerful designs, especially for generic scenarios.

Example: Factory Pattern


public interface IFactory<T>
{
    static abstract T Create();
}

public class Product : IFactory<Product>
{
    public static Product Create() => new Product();
}

This feature facilitates the implementation of design patterns like the factory pattern, enhancing code reusability and maintainability.


Conclusion

C# 11 introduces a range of powerful new features that enhance the expressiveness, performance, and usability of the language. These features simplify common coding patterns, reduce boilerplate code, and enable more flexible and robust application designs. By leveraging these new capabilities, developers can create more efficient and maintainable applications, driving productivity and innovation in their projects.

Next Post Previous Post
No Comment
Add Comment
comment url