9.0
C# 9.0, released with .NET 5 in November 2020, introduced several new features aimed at making the language more concise, expressive, and efficient for modern development practices. Here are some of the key features introduced in C# 9.0, along with examples and comparisons to earlier versions of C#:
1. Records
Records are a reference type that provides built-in functionality for encapsulating immutable data. They are ideal for scenarios where the primary purpose of the type is to store data with little to no behavior.
C# 9.0
C# < 9.0 Before C# 9.0, creating an immutable class required more boilerplate code, including manually implementing value equality.
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
protected bool Equals(Person other) => FirstName == other.FirstName && LastName == other.LastName;
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Person)obj);
}
public override int GetHashCode() => HashCode.Combine(FirstName, LastName);
}
2. Init Only Setters
Init only setters provide a way to make individual properties immutable after object construction, enhancing the support for immutable data types in C#.
C# 9.0
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
var person = new Person { FirstName = "John", LastName = "Doe" };
// person.FirstName = "Jane"; // Error: Init-only property or indexer 'Person.FirstName' can only be assigned in an object initializer
C# < 9.0 Previously, to achieve immutability for individual properties, you had to use readonly fields or properties with private setters, which could only be set in the constructor.
public class Person
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
3. Top-level Statements
Top-level statements reduce the boilerplate code required for simple programs, allowing you to write the main body of a program without the need for a Main
method.
C# 9.0
C# < 9.0
In earlier versions, a Main
method was required as an entry point for every console application.
4. Pattern Matching Enhancements
C# 9.0 introduced new pattern matching capabilities, such as simple type patterns, relational patterns, and logical patterns, making pattern matching more expressive and powerful.
C# 9.0
public static string GetDescription(object obj) => obj switch
{
> 0 and < 10 => "Between 0 and 10",
string s => $"String of length {s.Length}",
null => "Null",
_ => "Unknown"
};
C# < 9.0 Pattern matching was less expressive, and certain patterns required more verbose code or were not possible.
public static string GetDescription(object obj)
{
if (obj is int i && i > 0 && i < 10)
{
return "Between 0 and 10";
}
else if (obj is string s)
{
return $"String of length {s.Length}";
}
else if (obj == null)
{
return "Null";
}
else
{
return "Unknown";
}
}
5. Target-typed new
Expressions
C# 9.0 allows the type of the new
expression to be inferred from the context, reducing redundancy when the type is already known.
C# 9.0
C# < 9.0
Previously, you had to specify the type explicitly for each new
expression.