CSharp 10 Features

Last Updated: 4/1/2022

Struct Improvements

Parameterless Struct Constructors

  • Prior to C# 10, every struct had an implicit public parameterless constructor that set the struct’s fields to default. It was an error for you to create a parameterless constructor on a struct.
  • Starting in C# 10, you can include your own parameterless struct constructors. If you don’t supply one, the implicit parameterless constructor will be supplied to set all fields to their default.
  • Parameterless constructors you create in structs must be public and cannot be partial

Struct without Parameterless Constructor

public struct Measurement
{
	public double Value {get; set;}
	public string Description {get; set;}
	
	public override string ToString() => $"Value: {Value}, Description: {Description}";
}

var m1 = new Measurement();
Console.WriteLine(m1);
//Value: 0, Description:

Struct with Parameterless Constructor

public struct Measurement
{
	public double Value {get; set;}
	public string Description {get; set;}
	
	public Measurement() {
		Value = 5;
		Description = "Measurement";
	}
	
	public override string ToString() => $"Value: {Value}, Description: ({Description})";
}

var m1 = new Measurement();
Console.WriteLine(m1);
//Output:
//Value: 5, Description: Measurement

Creating Struct with default or array allocation

  • Structs that are created via default or array allocation ignores explicit parameterless constructors, and always set struct members to their default values.
  • Value-type fields to their default values (the 0-bit pattern) and all reference-type fields to null.
var m2 = default(Measurement);
Console.WriteLine(m2);
//Output: Value: 0, Description: ()

var ms = new Measurement[2];
Console.WriteLine(string.Join(", ", ms));
//Output: Value: 0, Description: (), Value: 0, Description: ()

Property Initializers

Beginning with C# 10, you can also initialize an instance field or property via property initializers

public string Description { get; init; } = "Measurement";

Without Explicit Constructors

When there is no explicit constructor in the struct, auto property initializer will work

public struct MeasurementWithoutConstructor
{
	public double Value {get; set;}
	public string Description {get; init;} = "Measurement";
	
	public override string ToString() => $"Value: {Value}, Description: ({Description})";
}

var mwoc1 = new MeasurementWithoutConstructor();
Console.WriteLine(mwoc1);
//Output: Value: 0, Description: (Measurement)  

var mwoc2 = default(MeasurementWithoutConstructor);
Console.WriteLine(mwoc2);
//Output: Value: 0, Description: ()

With Explicit Constructor

  • When there is explicit constructor and you call that constructor auto property Initializers will work
  • When there is explicit constructor and implicit parameterless constructor, if you call parameterless constructor auto property Initializers will not work
  • When there is explicit constructor and explicit parameterless constructor, if you call parameterless constructor auto property Initializers will work
public struct MeasurementWithConstructor
{
	public double Value {get; set;}
	public string Description {get; init;} = "Measurement";
	
	//uncomment this for auto property initializers to work
	//public MeasurementWithConstructor() {
	//	Value = 1;
	//}
	
	public MeasurementWithConstructor(double value) {
		Value = value;
	}
	
	public override string ToString() => $"Value: {Value}, Description: ({Description})";
}

var mwc1 = new MeasurementWithConstructor();
Console.WriteLine(mwc1);
//Without parametersless constructor
//Output - Value: 0, Description: ()
//With parameterless constructor  
//Output - Value: 1, Description: (Measurement)

var mwc2 = new MeasurementWithConstructor(5);
Console.WriteLine(mwc2);
//Output - Value: 5, Description: (Measurement)  

var mwc3 = default(MeasurementWithConstructor);
Console.WriteLine(mwc3);
//Output - Value: 0, Description: ()

Example

record structs

  • You can use the record struct keyword to define a value type that provides built-in functionality for encapsulating data.

Create record with mutable properties

A positional record struct declares read-write properties.

Positional Property Syntax

public record struct Person(string FirstName, string LastName);

Standard Property Syntax

public record Person
{
    public string FirstName { get; set; } = default!;
    public string LastName { get; set; } = default!;
};

Create record with immutable properties

A positional readonly record struct declare init-only properties.

Positional Property Syntax

public readonly record struct Point(double X, double Y, double Z);

Standard Property Syntax

public record struct Point
{
    public double X {  get; init; }
    public double Y {  get; init; }
    public double Z {  get; init; }
}

Immutability

  • Immutability can be useful when you need a data-centric type to be thread-safe or you're depending on a hash code remaining the same in a hash table.
  • Immutability isn't appropriate for all data scenarios. Entity Framework Core, doesn't support updating with immutable entity types.

Value equality

  • For record types, including record struct and readonly record struct, two objects are equal if they are of the same type and store the same values.
  • The definition of equality for a record struct is the same as for a struct. The difference is that for a struct, the implementation is in ValueType.Equals(Object) and relies on reflection. For records, the implementation is compiler synthesized and uses the declared data members.
  • To implement value equality, the compiler synthesizes the following methods:
    • An override of Object.Equals(Object)
    • A virtual Equals method whose parameter is the record type. This method implements IEquatable<T>
    • An override of Object.GetHashCode().
    • Overrides of operators == and !=.
public record struct Person(string FirstName, string LastName);

Person p1 = new Person("Vijay", "Kumar");
Person p2 = new Person("Vijay", "Kumar");
	
Console.WriteLine(p1 == p2);
//Output - True

Nondestructive mutation

  • If you need to copy an instance with some modifications, you can use a with expression to achieve nondestructive mutation.
  • A with expression makes a new record instance that is a copy of an existing record instance, with specified properties and fields modified. - - You use object initializer syntax to specify the values to be changed
Person p3 = p1 with { LastName = "Mahesh" };
Person p4 = p1 with { FirstName = "Siva" };

Built-in formatting for display

Record types have a compiler-generated ToString method that displays the names and values of public properties and fields.

Person p1 = new Person("Vijay", "Kumar");
Console.WriteLine(p1);
//Output - Person { FirstName = Vijay, LastName = Kumar }

Deconstructor behavior

Person p1 = new Person("Vijay", "Kumar");
var (firstName, lastName) = p1;
Console.WriteLine(firstName + ", " + lastName);

Example

References