Changes In Struct In C# 10

The Struct's has undergone some interesting language changes in C# 10. In this blog post, we will look into some of them and understand their significance.

Parameterless Constructor

One of the major and noticeable changes as far struct is concerned is the introduction of support for explicit parameterless constructors. For example, the following code was not compilable.

public struct Foo {
    public Foo() => (FirstName, LastName) = ("John", "Doe");
    public string FirstName {
        get;
        set;
    }
    public string LastName {
        get;
        set;
    }
}

The Language developers have now taken off that restriction allowing the developers to define explicit constructors for their structures. Let us look at one example.

public struct Foo {
    public Foo() => (FirstName, LastName) = ("John", "Doe");
    public string FirstName {
        get;
        set;
    }
    public string LastName {
        get;
        set;
    }
}
Foo foo = new();
Console.WriteLine($ "FirstName : {foo.FirstName}, LastName : {foo.LastName}");
// Output
FirstName: John, LastName: Doe

The above code is perfectly valid in C# 10, thanks to support for explicit constructors. However, care should be given to ensure all the fields are initialized in the constructor. For example, the following code is invalid.

public struct Foo
{
    public Foo() => FirstName= "John";
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

This would raise an exception complaining that the LastName property should be non-null or fully assigned before the control returns the caller.

This brings us an important difference that developers need to be aware of when migrating from C# 9 to C# 10. Consider the following code.

Foo foo1 = new ();
Foo foo2 = default;
	
Console.WriteLine($"foo1 Equals foo2 : {Equals(foo1,foo2)}");

The above code has provided a true for Equals check for any struct in C# 9.0. However, when it comes to C# 10, one cannot be sure unless one check the source to ensure the default constructor is not explicitly defined. If the explicit default constructor is defined, the fields would be initialized and could be having a different value.

Field Initializers

The struct in C# 9.0 wouldn't allow the developers to use fields initializers. The following code was invalid in C# 9.

public struct Foo {
    public string FirstName {
        get;
        set;
    } = "John";
    public string LastName {
        get;
        set;
    } = "Doe";
}

This again has undergone a change with C# 10, with the support for field initializers. The following code would provide us the same result as in the previous example with explicit constructor.

public struct Foo {
    public string FirstName {
        get;
        set;
    } = "John";
    public string LastName {
        get;
        set;
    } = "Doe";
}
Foo foo = new();
Console.WriteLine($ "FirstName : {foo.FirstName}, LastName : {foo.LastName}");
// Output
FirstName: John, LastName: Doe

Support for With Expression

Another key improvement for struct in C# 10 is support for with expresison. In C# 9, record was introduced in C# as a way for creating immutable data structures with immutable properties. The with expressions allowed the developers to create clones of existing record with subtle changes. For example

public record Person(string FirstName, string LastName);
var johnDoe = new {
    FirstName = "John",
        LastName = "Doe"
};
var janeDoe = johnDoe with {
    FirstName = "Jane"
};

The instance janeDoe is a new object created with its properties having the same value as johnDoe except for the FirstName, which has been given a new value.

The language team has now added support for using with expression for struct as well. For example, the following code is perfectly valid with C# 10.

Foo foo1 = new ()
{
    FirstName = "John",
    LastName = "Doe"
};
var foo2 = foo1 with { FirstName = "Jane"};
foo1=>FirstName : John, LastName : Doe
foo2=>FirstName : Jane, LastName : Doe

where Foo is defined as

public struct Foo
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

The fact the language team has decided to use the with expression outside the scope of record is a great way. It would have been a shame if such a powerful tool cannot be used with other data structures. In fact, the with expression is also supported by anonymous types as well now.

var foo1 = new 
{
    FirstName = "John",
    LastName = "Doe"
};
var foo2 = foo1 with { FirstName = "Jane"};

This is really awesome.

So, in this blog post, we visited some of the changes developers could see in struct with C# 10. To be honest, the way language is evolving is so great to see.