.NET 5 C#

Things About C# 9.0 New Feature 〝record〞

Matt 2020/12/23 00:32:18
507

As the .Net 5 came to the world, and C# 9.0 came with it. C# 9.0 adds the following features and enhancements to the C# language:

  • Records
  • Init only setters
  • Top-level statements
  • Pattern matching enhancements
  • Native sized integers
  • Function pointers
  • Suppress emitting localsinit flag
  • Target-typed new expressions
  • static anonymous functions
  • Target-typed conditional expressions
  • Covariant return types
  • Extension GetEnumerator support for foreach loops
  • Lambda discard parameters
  • Attributes on local functions
  • Module initializers
  • New features for partial methods

Today, we will focus on the new feature, Records of C# 9.0. And what is it ? What's the benefit of it ?

A record type, which is a reference type that provides synthesized methods to provide value semantics for equality. Records are immutable by default.

Let's make an easy example to obtain what is that mean ?

class Program
{
    static void Main(string[] args)
    {
        var personC = new PersonOfClass("First Name", "Last Name");
        Console.WriteLine($"PersonOfClass ToString(): {personC}");

        Console.WriteLine();
        Console.WriteLine("======================================");
        Console.WriteLine();

        // this sentence: Target-typed new expression
        PersonOfRecord personR = new ("First Name", "Last Name");
        Console.WriteLine($"PersonOfRecord ToString(): {personR}");
    }
}

public class PersonOfClass
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // if you are feel STRANGE about these sentences, please study about value types, and deconstruct
    public PersonOfClass(string firstName, string lastName) 
        => (FirstName, LastName) = (firstName, lastName);
}

// a common way defining a record
public record PersonOfRecord
{
    public string FirstName { get; }
    public string LastName { get; }

    public PersonOfRecord(string firstName, string lastName)
        => (FirstName, LastName) = (firstName, lastName);
}

We would see something different while run the code.

img "C# 9.0 class - record"

But why ? We could find out something interesting by observing IL code.

PersonOfClass

img "cs9 PersonOfClass"

PersonOfRecord

img "cs9 PersonOfRecord"

As the IL code showing us,

  • a Record is also a class
  • a Record's properties are readonly
  • a Record overwritten many base methods, such as ToString()
  • a Record override for GetHashCode()
  • a Record's methods for value-based equality comparisons
  • a Record copy and clone members

Any of the synthesized members except the "clone" method may be written by you.

Okay, we will keep diving into it, let's do something magic, a quick / better way to define a record,

public record QuickWyDefneRecord(string PropertyA, int AnotherProperty);

It looked like a constructor, but parameters come with Pascal case, not Camel case. Because of we treat them here as properties.

Same thing, observing the IL code, we will understand the meanings.

QuickWyDefneRecord

img "QuickWyDefneRecord IL"

As the IL code showing us,

  • a Record implement Deconstruct

Note that, the property came with a init setter. It's one of C# 9.0 new features, Init only setters. Init only setters provide consistent syntax to initialize members of an object. Property initializers make it clear which value is setting which property.

One more thing, records support with expressions. A with expression instructs the compiler to create a copy of a record, but with specified properties modified:

PersonOfRecord clone = person with { };

clone record will be an copy of person, and all members are the same.

PersonOfRecord brother = person with { FirstName = "Paul" };

brother record will be another copy of person, FirstName would be modified to Paul.

Starting with another example to watch them.

class Program
{
    static void Main(string[] args)
    {
        var c1a = new PersonClass("Tim", "Corey");
        var c1b = new PersonClass("Tim", "Corey");
        var c1c = new PersonClass("Jasmine", "Choi");

        Console.WriteLine($"c1a.GetHashCode(): { c1a.GetHashCode() }");
        Console.WriteLine($"c1b.GetHashCode(): { c1b.GetHashCode() }");
        Console.WriteLine($"c1c.GetHashCode(): { c1c.GetHashCode() }");
        Console.WriteLine();

        Console.WriteLine($"c1a.ToString(): { c1a }");
        Console.WriteLine($"c1b.ToString(): { c1b }");
        Console.WriteLine($"c1c.ToString(): { c1c }");
        Console.WriteLine();

        Console.WriteLine($"Equals(c1a, c1b): { Equals(c1a, c1b) }");
        Console.WriteLine($"Equals(c1a, c1c): { Equals(c1a, c1c) }");
        Console.WriteLine();

        Console.WriteLine($"c1a == c1b: { c1a == c1b }");
        Console.WriteLine($"c1a == c1c: { c1a == c1c }");
        Console.WriteLine();

        Console.WriteLine();
        Console.WriteLine("======================================");
        Console.WriteLine();

        PersonRecord r1a = new("Tim", "Corey");
        PersonRecord r1b = new("Tim", "Corey");
        PersonRecord r1c = new("Jasmine", "Choi");

        Console.WriteLine($"r1a.GetHashCode(): { r1a.GetHashCode()} ");
        Console.WriteLine($"r1b.GetHashCode(): { r1b.GetHashCode()} ");
        Console.WriteLine($"r1c.GetHashCode(): { r1c.GetHashCode()} ");
        Console.WriteLine();

        Console.WriteLine($"r1a.ToString(): { r1a }");
        Console.WriteLine($"r1b.ToString(): { r1b }");
        Console.WriteLine($"r1c.ToString(): { r1c }");
        Console.WriteLine();

        Console.WriteLine($"Equals(r1a, r1b): { Equals(r1a, r1b) }");
        Console.WriteLine($"Equals(r1a, r1c): { Equals(r1a, r1c) }");
        Console.WriteLine();

        Console.WriteLine($"r1a == r1b: { r1a == r1b }");
        Console.WriteLine($"r1a == r1c: { r1a == r1c }");
        Console.WriteLine();

        var clone = r1a with { };
        Console.WriteLine($"clone.ToString(): { clone }");
        Console.WriteLine($"Equals(clone, r1a): { Equals(clone, r1a) }");
        Console.WriteLine($"clone == r1a: { clone == r1a }");
        Console.WriteLine();

        var brother = r1a with {
            FirstName = "Paul"
        };
        Console.WriteLine($"brother.ToString(): {brother}");
        Console.WriteLine($"Equls(brother, r1a): {Equals(brother, r1a)}");
        Console.WriteLine($"brother == r1a: { brother == r1a }");
        Console.WriteLine();
    }
}

internal class PersonClass
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public PersonClass(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

internal record PersonRecord(string FirstName, string LastName);

Now, it's your turn to tell the result of results.

the result of results

img "example results"

Enjoy it.


cover image come from here


References:

What's new in C# 9.0

Create record types

Value types (C# reference)

Deconstruct

C# 7 分解式(deconstructor)

Matt