【C#/.NET】record介紹

2023-06-06 15:06:58

 目錄

 

什麼是record?

使用record

record解構

record原理

結論


 

什麼是record?

record是.NET 5中的一種新特性,可以看作是一種概念上不可變的類。records可以幫助我們在C#中更容易地處理資料,同時提供了重要的功能,如物件相等性、hashcode和解構。

與類不同,records具有值語意。也就是說,當比較兩個records的範例時,比較的是這些範例的屬性而非參照。這意味著,如果兩個records的屬性值相同,它們就是相等的。

record也可以簡化需要類似於Dto的資料結構容器的定義。

使用record

Person p1 = new("小明", "南山", "[email protected]");
Person p2 = new("小明", "南山", "[email protected]");

Console.WriteLine(p1 == p2);
public record Person(string Name, string Address, string Email);

像定義一個類一樣,public class Person,只是將class關鍵字替換成record關鍵字。然後屬性是用括號來定義。

預設的record宣告是class,如果想宣告一個struct

public record struct Person(string Name, string Address, string Email);

 

record是不可變的型別,括號中宣告的屬性在構造之後不可變更。可以使用==按屬性的值進行比較。可以直接作為hash的key以及結構。

record可以像普通類一樣擴充套件可變更的屬性和自定義的方法,語法如下

public record Person(string Name, string Address, string Email)
{
    public required string PhoneNumber { get; set; }
    public static IEnumerable<Person> GetAll()
    {
        yield return new Person("張三", "123 Main St", "[email protected]") { PhoneNumber = "123456789"};
        yield return new Person("李四", "456 Elm St", "[email protected]") { PhoneNumber = "123456789" };
        yield return new Person("王二", "789 Oak St", "[email protected]") { PhoneNumber = "123456789" }; ;
    }

    public string GetDisplayName() => $"{Name} ({Email})";
};

 

record解構

record可以通過解構,將物件解構為元組,方便一次性獲取record中的屬性值,

Person p1 = new("小明", "南山", "[email protected]") ;

var (name,address,email) = p1 ;

 

record原理

record的原理是編譯器提供支援,上述Person定義反編譯結果如下

public class Person : IEquatable<Person>
{
    [CompilerGenerated]
    protected virtual Type EqualityContract
    {
        [CompilerGenerated]
        get
        {
            return typeof(Person);
        }
    }

    public string Name { get; set/*init*/; }

    public string Address { get; set/*init*/; }

    public string Email { get; set/*init*/; }

    public Person(string Name, string Address, string Email)
    {
        this.Name = Name;
        this.Address = Address;
        this.Email = Email;
        base..ctor();
    }

    [CompilerGenerated]
    public override string ToString()
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append("Person");
        stringBuilder.Append(" { ");
        if (PrintMembers(stringBuilder))
        {
            stringBuilder.Append(' ');
        }
        stringBuilder.Append('}');
        return stringBuilder.ToString();
    }

    [CompilerGenerated]
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        RuntimeHelpers.EnsureSufficientExecutionStack();
        builder.Append("Name = ");
        builder.Append((object?)Name);
        builder.Append(", Address = ");
        builder.Append((object?)Address);
        builder.Append(", Email = ");
        builder.Append((object?)Email);
        return true;
    }

    [CompilerGenerated]
    public static bool operator !=(Person? left, Person? right)
    {
        return !(left == right);
    }

    [CompilerGenerated]
    public static bool operator ==(Person? left, Person? right)
    {
        return (object)left == right || (left?.Equals(right) ?? false);
    }

    [CompilerGenerated]
    public override int GetHashCode()
    {
        return ((EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Address)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Email);
    }

    [CompilerGenerated]
    public override bool Equals(object? obj)
    {
        return Equals(obj as Person);
    }

    [CompilerGenerated]
    public virtual bool Equals(Person? other)
    {
        return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other.Name) && EqualityComparer<string>.Default.Equals(Address, other.Address) && EqualityComparer<string>.Default.Equals(Email, other.Email));
    }

    [CompilerGenerated]
    protected Person(Person original)
    {
        Name = original.Name;
        Address = original.Address;
        Email = original.Email;
    }

    [CompilerGenerated]
    public void Deconstruct(out string Name, out string Address, out string Email)
    {
        Name = this.Name;
        Address = this.Address;
        Email = this.Email;
    }
}

 

 

 可以看到,編譯器給使用了record關鍵字的定義生成了對應的屬性和建構函式,並且重寫了ToString(),GetHashCode,Equals還有一個解構函數和!=和==運運算元。其實看到這裡就明白了,為什麼record可以提供值比較,解構,hash等。

不可變性是因為record的屬性是使用了init關鍵字而不是set,這樣子如果對record的物件屬性賦值,編譯器會報錯。

值相等性是重定義了!=和==運運算元

hash是因為重寫了GetHashCode,Equals

解構是定義了Deconstruct方法

結論

我們介紹了.NET 5中引入的record型別及其優點。但對於許多資料物件的簡單情況,如值物件和DTO,推薦使用record型別。雖然record可以定義可變更的屬性和新增方法,不過這樣子有點違背了record的初衷。