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
andreadonly record struct
, two objects are equal if they are of the same type and store the same values. - The definition of
equality
for arecord struct
is the same as for astruct
. The difference is that for a struct, the implementation is inValueType.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!=
.
- An override of
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
- https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#parameterless-constructors-and-field-initializers
- https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init
- https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record