C# 9 record is not sugar for simple property POCO
Today I upgrade project to .NET 5 and try to rewrite package using C#9 syntax. I found that the previous thought for record is just a simple property POCO’s sugar is wrong.
Here, simple property POCO
is defined as public class Type {public string ID{get;set}/*..*/}
simple class code.
1. rocord is indeed a class after compiler, but it is not a simple property POCO class.
Look at the IL Spy decompilation code and find that the system has done a lot for it.
2. Default property is {get;init;}
not {get;set;}
, this means setting moment is in constructor
, and that represent record by default is immutable
and thread-safe
, because the same value is achieved.
So when you use the Dapper similar framework to query the POCO data and want to modify the attributes, it will report the CS8852
Unable to modify error.
3. Change of Equals
default logic .
You can see the example written by TimCorey , see the == difference between the default class and record, online test link
public class Program
{
public static void Main()
{
var record1Obj1 = new record1(FirstName: "Lin", LastName: "WeiHan");
var record1Obj2 = new record1(FirstName: "Lin", LastName: "WeiHan");
Console.WriteLine(record1Obj1 == record1Obj2);//true
var class1Obj1 = new Class1() { FirstName = "Lin", LastName = "WeiHan" };
var class2Obj2 = new Class1() { FirstName = "Lin", LastName = "WeiHan" };
Console.WriteLine(class1Obj1 == class2Obj2);//false
}
}
public record record1(string FirstName,string LastName);
public class Class1
{
public string FirstName {get;init;}
public string LastName{get;init;}
}
Because the record override ==
with Equals
that as long as the same record type, and propertys value are same
the system will be identified as true
, which is commonly known structural equality
, can look IL Spy decompile the code
public virtual bool Equals(record2? other)
{
return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(FirstName, other!.FirstName) && EqualityComparer<string>.Default.Equals(LastName, other!.LastName);
}
It is different from the object class default to get RuntimeHelpers.GetHashCode
Handle logic.
4. GetHashCode did similar logic, therefore propertys value are same,hashCode'll be same'
, test link
IL Spy decompile Code
public override int GetHashCode()
{
return (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FirstName)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(LastName);
}
5. Note that you can't treat record as a must immutable
, because the reason is like below code that is unlimited...
public record record2
{
public string FirstName {get;set;}
public string LastName{get;set;}
}
Allow {get;init;}
to {get;set;}
, will lose immutable
and thread-safe
behavior.
6. Record will help generate a readable ToString implementation. The
following image shows the difference between the ToString generated by a general class and a record
7. Record helps to generate extend IEquatable<Type>
and implement strong typing, public virtual bool Equals(Record1? other)
which means it can avoid the original public override bool Equals(object? obj)
need unboxing and boxing performane
problems
Read information:
Top comments (2)
Thanks for posting this! I’ve been trying to understand more about the behind-the-scenes workings of C# / .NET, and this breakdown has really helped with that.
It's my pleasure 😉