C# 10 Records

2023-02-03 C# record GetHashCode Equals HashSet with

I prefer immutable types when designing my code, so usually ended up with a class where all properties are getters only and constructor to fill them on initialization. Something like this

public class Anchor
{
    public Anchor(string anchor, string file)
    {
        AnchorName = anchor;
        FileName = file;
    }

    public string AnchorName { get; }
    public string FileName { get; }

    public override bool Equals(object obj)
    {
        return obj is Anchor anchor &&
                AnchorName == anchor.AnchorName &&
                FileName == anchor.FileName;
    }

    public override int GetHashCode()
    {
        int hashCode = -1749314865;
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AnchorName);
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FileName);
        return hashCode;
    }
}

The class able also implements Equals to compare the contents and GetHashCode to use with data types like HashSet.

Since C# 9 it is possible to use init modifier to properties, allowing to init them with curly bracket syntax and saving some typing (auto-generating with Visual Studio really) for constructors

public class Anchor
{
    public string AnchorName { get; init; }
    public string FileName { get; init; }

    // Equals and GetHashCode as before ...
}

With C# 10 this moved a bit further with introduction of records. All implementation above can be done with just

public record class Anchor(string AnchorName, string FileName);

And we get some bonus to above

  • autogenerated ToString implementation to print something like Anchor { AnchorName = ID_002, FileName = Requirements02.docx }
  • autogenerated comparators considering all properties
  • hashing using all properties
  • non-destructive mutator with

Here is example of keeping just unique entries

HashSet<Anchor> found = new HashSet<Anchor>();
found.Add(new Anchor("ID_001", "Requirements01.docx"));
found.Add(new Anchor("ID_002", "Requirements02.docx"));
found.Add(new Anchor("ID_001", "Requirements01.docx"));

foreach(Anchor a in found)
    Console.WriteLine(a);

And cloning entry with mutation of some properties

var a1 = new Anchor("ID_003", "Requirements01.docx");
var a2 = a1 with { AnchorName = "ID_004" };
Console.WriteLine($"{a1} and {a2}");