1. Record Types
Record types were introduced in C# 9.0 as a way to create immutable types with built-in support for value-based equality. Records are particularly useful for defining simple data structures where immutability and equality based on the value of properties (rather than reference) are desired.
Example: Record Types
public record Person(string FirstName, string LastName);
class Program
{
static void Main()
{
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");
Console.WriteLine(person1 == person2); // Output: True (value-based equality)
Console.WriteLine(person1); // Output: Person { FirstName = John, LastName = Doe }
}
}
- Explanation: In this example,
Person
is a record type with two properties,FirstName
andLastName
. The equality comparison (==
) checks the values of these properties, not their references. The defaultToString
method also provides a string representation of the record's values.
2. Init-Only Properties
Init-only properties, introduced in C# 9.0, allow you to create properties that can only be set during object initialization. This ensures that the property values remain immutable after the object is constructed.
Example: Init-Only Properties
public class Car
{
public string Make { get; init; }
public string Model { get; init; }
}
class Program
{
static void Main()
{
var car = new Car { Make = "Toyota", Model = "Corolla" };
// car.Make = "Honda"; // Error: Cannot modify an init-only property
Console.WriteLine($"{car.Make} {car.Model}"); // Output: Toyota Corolla
}
}
- Explanation: The
Car
class hasMake
andModel
properties that areinit
-only. They can only be set during initialization and are immutable afterward.
3. Top-Level Programs
Top-level programs, introduced in C# 9.0, simplify the entry point of a console application by allowing you to omit the Main
method and directly write code at the top level.
Example: Top-Level Programs
using System;
Console.WriteLine("Hello, World!");
- Explanation: In this example, there’s no
Main
method. TheConsole.WriteLine
statement is at the top level, and the compiler automatically generates theMain
method behind the scenes. This is particularly useful for small or simple programs.
4. With Expressions
With expressions in C# 9.0 allow you to create a copy of an object with modifications to some of its properties. This is particularly useful for records and immutable types.
Example: With Expressions
public record Person(string FirstName, string LastName);
class Program
{
static void Main()
{
var person1 = new Person("John", "Doe");
var person2 = person1 with { LastName = "Smith" };
Console.WriteLine(person1); // Output: Person { FirstName = John, LastName = Doe }
Console.WriteLine(person2); // Output: Person { FirstName = John, LastName = Smith }
}
}
- Explanation: The
with
expression creates a newPerson
object,person2
, by copyingperson1
and modifying theLastName
property. The originalperson1
remains unchanged.
5. Enhanced Pattern Matching
C# 9.0 introduces enhancements to pattern matching, including new patterns such as relational patterns, logical patterns, and more. These patterns make it easier to express complex conditions in a concise manner.
Example: Enhanced Pattern Matching
class Program
{
static string ClassifyNumber(int number) => number switch
{
< 0 => "Negative",
0 => "Zero",
> 0 and <= 10 => "Small Positive",
> 10 => "Large Positive",
_ => "Unknown"
};
static void Main()
{
Console.WriteLine(ClassifyNumber(-5)); // Output: Negative
Console.WriteLine(ClassifyNumber(5)); // Output: Small Positive
Console.WriteLine(ClassifyNumber(15)); // Output: Large Positive
}
}
- Explanation: This example uses relational patterns (
< 0
,> 0 and <= 10
) and logical patterns (and
) to classify a number. The enhanced pattern matching makes the switch expression more expressive and readable.
6. Covariant Returns
Covariant returns, introduced in C# 9.0, allow an overriding method to return a more derived type than the method it overrides. This provides more flexibility and type safety in object-oriented design.
Example: Covariant Returns
public class Animal
{
public virtual Animal GetAnimal() => new Animal();
}
public class Dog : Animal
{
public override Dog GetAnimal() => new Dog(); // Covariant return
}
class Program
{
static void Main()
{
Dog dog = new Dog();
Animal animal = dog.GetAnimal(); // This returns a Dog, but can be assigned to an Animal variable
Console.WriteLine(animal.GetType().Name); // Output: Dog
}
}
- Explanation: The
Dog
class overrides theGetAnimal
method of theAnimal
class and returns a more derived type (Dog
). This is an example of covariant return types, where the overridden method can return a more specific type than the base method.
Summary
- Record Types: Introduce immutable types with value-based equality, making it easier to work with data-centric objects that should not change after creation.
- Init-Only Properties: Allow properties to be set only during object initialization, ensuring that they remain immutable afterward.
- Top-Level Programs: Simplify the syntax for small programs by allowing code to be written directly at the top level, without the need for a
Main
method. - With Expressions: Enable the creation of new objects by copying existing ones and modifying specific properties, particularly useful for immutable types.
- Enhanced Pattern Matching: Extend pattern matching capabilities with relational and logical patterns, making it easier to express complex conditions concisely.
- Covariant Returns: Allow overriding methods to return more derived types, providing more flexibility in method return types while maintaining type safety.