Evolving Code: Transitioning from C# 10 to C# 11 with Before and After Examples

Pedro Alicea
4 min readNov 25, 2023

--

Introduction

The evolution from C# 10 to C# 11 brings forth significant enhancements that streamline coding practices. This article compares these two versions, showcasing before and after examples to illustrate the practical benefits of adopting C# 11’s new features.

1. Raw String Literals

C# 10:

// Using traditional string with escape sequences
string json = "{\"name\": \"John\", \"age\": 30}";

C# 11:

// Using raw string literals for better clarity
string json = """
{
"name": "John",
"age": 30
}
""";

Benefit: Raw string literals in C# 11 eliminate the need for escape sequences, making strings easier to read and maintain, especially in file paths and JSON.

2. List Patterns

C# 10:

int[] numbers = { 1, 2, 3 };
if (numbers.Length == 3 && numbers[0] == 1 && numbers[1] == 2 && numbers[2] == 3)
{
Console.WriteLine("Match found!");
}

C# 11:

int[] numbers = { 1, 2, 3 };
if (numbers is [1, 2, 3])
{
Console.WriteLine("Match found!");
}

Benefit: List patterns in C# 11 simplify array and list matching, reducing code complexity and improving readability.

3. Required Members

C# 10:

public class Person
{
public string Name { get; init; }
}
var person = new Person(); // No compile-time error, but Name is null

C# 11:

public class Person
{
public required string Name { get; init; }
}
var person = new Person(); // Compile-time error

Benefit: Required members in C# 11 ensure critical properties are initialized, enhancing the safety and predictability of your objects.

4. Enhanced Nullable Reference Types

C# 10:

string? nullableString = null;
if (nullableString != null)
{
Console.WriteLine(nullableString.Length);
}

C# 11:

string? nullableString = null;
if (nullableString is not null)
{
Console.WriteLine(nullableString.Length);
}

Benefit: The enhanced nullable reference types in C# 11 provide more intuitive syntax and clearer intent for null checks, further improving code safety.

5. Struct Parameterless Constructors

C# 10:

public struct Point
{
public int X, Y;
// No parameterless constructor allowed
}
var p = new Point(); // X and Y are default-initialized to 0

C# 11:

public struct Point
{
public int X, Y;
public Point() { X = 1; Y = 1; }
}
var p = new Point(); // X and Y are initialized to 1

Benefit: Parameterless constructors in structs allow for better control over default values, providing more flexibility and predictability in struct initialization.

6. Generic Attributes

C# 10:

public class CustomAttribute : Attribute
{
public CustomAttribute(Type type) => TargetType = type;
public Type TargetType { get; }
}
[CustomAttribute(typeof(int))]
public int Calculate() => 42;

C# 11:

public class GenericTypeAttribute<T> : Attribute { }
[GenericTypeAttribute<int>()]
public int Calculate() => 42

Benefit: Generic attributes in C# 11 provide a more streamlined and type-safe way to define attributes, particularly when a System.Type parameter is involved.

7. Numeric IntPtr and UIntPtr

C# 10:

IntPtr pointer = new IntPtr(5);
UIntPtr unsignedPointer = new UIntPtr(10);

C# 11:

nint pointer = 5;
nuint unsignedPointer = 10;

Benefit: Using nint and nuint for IntPtr and UIntPtr makes the code more readable and familiar, especially for developers used to working with native integer types.

8. Improved Method Group Conversion to Delegate

C# 10:

Action actionDelegate = new Action(ExternalMethod);

C# 11:

Action actionDelegate = ExternalMethod; // Delegate object can be cached

Benefit: The ability of the C# 11 compiler to cache delegate objects created from method group conversions can improve performance by reducing the need for repeated delegate instantiations.

9. Auto-default Struct

C# 10:

public struct Vector
{
public double X, Y;
// Explicit initialization required
}

C# 11:

public struct Vector
{
public double X, Y;
// Compiler ensures default initialization
}

Benefit: This feature ensures that all struct fields are initialized, thereby reducing the risk of uninitialized data and improving the safety and predictability of structs.

10. ref Fields and Scoped ref Variables

C# 10:

ref struct RefStructExample
{
// Define fields...
}

C# 11:

ref struct RefStructExample
{
public ref int RefField;
public void Modify()
{
ref int localRef = ref RefField;
scoped ref int scopedLocalRef = ref RefField; // Scoped reference
}
}

Benefit: The introduction of ref fields and scoped ref variables in C# 11 enhances memory management and control over the scope and lifetime of references.

11. File Local Types

C# 10:

// Types visible outside the file
public class FileClass { }

C# 11

file class FileClass { } // Restricted to file scope

Benefit: File local types help to organize code and prevent naming collisions, especially in larger projects or when using source generators.

Conclusion

The transition from C# 10 to C# 11 offers developers an opportunity to write more efficient, expressive, and safer code. By understanding and implementing these new features, you can significantly elevate the quality of your C# applications.

If you find the post useful, consider buying me a coffee.

About the Author: As an experienced technology expert and former software architect, I specialize in designing cutting-edge solutions using the latest technologies. With a deep understanding of .NET and its evolving landscape, I am passionate about sharing knowledge and insights with the development community.

--

--