3.12C#类型之记录结构(record struct)和记录类(record class)

1.记录声明

声明记录结构(值类型)或记录类(引用类型)的语法与声明结构或类的语法相似,可使用位置参数或属性语法声明记录类型。

记录类型可以是不可变类型,也可以是可变类型,但记录类型的发明主要是为创建自定义不可变类型提供简洁的语法。

//记录结构(不可变类型)
readonly record struct 结构名(类型 参数名);
//等同于
record struct 结构名
{
    public 类型 属性名 { get; init; }
}
//记录类(不可变类型)
record [class] 类名(类型 参数名);
//等同于
record [class] 类名
{
    public 类型 属性名 { get; init; } = default!;
}
//记录结构(可变类型)
record struct 结构名(类型 参数名);
//等同于
record struct 结构名
{
    public 类型 属性名 { get; set; }
}
//记录类(可变类型)
record [class] 类名
{
    public 类型 属性名 { get; set; } = default!;
};

编译器会为记录的每个位置参数自动创建public自动实现的属性,如果生成的自动实现的属性定义并不是您所需要的,您可以自行定义同名的属性从而重写。

//更改可访问性
public record Person(string FirstName, string LastName, string Id)
{
    internal string Id { get; init; } = Id;
}

另外,也可以新增其它字段或属性。

//新增属性
public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; } = Array.Empty<string>();
};

2.继承

记录结构不支持继承,因为结构不支持继承。

记录类支持继承,与表达继承关系的类语法相同。记录与类之间不可相互继承。

3.创建对象

记录结构或记录类的对象创建方式与结构或类相似。

public record Person(string FirstName, string LastName);

public static void Main()
{
    Person person = new("Nancy", "Davolio");
    Console.WriteLine(person);
    //输出: Person { FirstName = Nancy, LastName = Davolio }
}

4.不可变类型(immutable type)

无论是通过位置参数(record class 和 readonly record struct)创建的,还是通过自定义 init 访问器创建的,init 属性都具有浅的不可变性。初始化后,将不能更改值类型属性的值或引用类型属性的引用。不过,引用类型属性(可变类型)引用的数据是可以更改的。

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    Person person = new("Nancy", "Davolio", new string[1] { "555-1234" });
    Console.WriteLine(person.PhoneNumbers[0]);
    //输出: 555-1234

    person.PhoneNumbers[0] = "555-6789";
    Console.WriteLine(person.PhoneNumbers[0]);
    //输出: 555-6789
}

当您需要一种类型是线程安全的或者您依赖于在哈希表中保持相同的哈希码时,不可变类型很有用。

不可变类型并不适用于所有数据场景。例如,Entity Framework Core不支持使用不可变实体类型进行更新。

5.记录类型的值相等(value equality)

值类型往往是比较值相等,而引用类型是比较引用相等,此时如果想比较结构或类的值相等,记录类型的发明是为了解决此问题而生。

引用相等(reference equality):使用 ReferenceEquals() 方法比较一个记录类型的两个变量,如果两个变量引用相同的对象,则这两个变量 引用相等

记录类型的值相等(value equality) :使用 Object.Equals(Object) 方法(同等的运算符==)比较一个记录类型的两个变量,如果两个变量的记录类型匹配且依据不同的数据类型相等运算符比较规则的前提下所有字段和属性的值相等,则这两个变量 值相等

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    var phoneNumbers = new string[2];
    Person person1 = new("Nancy", "Davolio", phoneNumbers);
    Person person2 = new("Nancy", "Davolio", phoneNumbers);
    Console.WriteLine(person1 == person2); 
    //类型都是Person,依据字符串和数组的相等运算符比较规则,输出:True

    person1.PhoneNumbers[0] = "555-1234";
    Console.WriteLine(person1 == person2); 
    //phoneNumbers数组引用同一个对象,输出:True

    Console.WriteLine(ReferenceEquals(person1, person2)); 
    //person1和person2属于两个独立的新建的对象,所以不引用同一个对象,输出:False
}

并非所有数据模型都适用于 值相等。例如,Entity Framework Core依赖于 引用相等 来确保它只使用实体类型的一个实例来表示概念上的一个实体。因此,记录类型不适合用作 Entity Framework Core 中的实体类型。

6.非破坏性变化(nondestructive mutation)

非破坏性变化(nondestructive mutation)引用的是函数式编程里面的概念,意思是当您想要更改一个对象的状态时,创建原数据的拷贝与修改从而合成新数据优于直接修改原数据。

with表达式会创建新的对象,此新的对象是已存在对象的副本与修改的特定属性和字段合成的新对象。使用对象初始值设定项语法来指定要修改的成员及其新值。

对于引用类型成员,在复制操作数时仅复制对成员实例的引用。

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; }
}

public static void Main()
{
    Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] };
    Console.WriteLine(person1);
    //输出:Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }

    Person person2 = person1 with { FirstName = "John" };
    Console.WriteLine(person2);
    //输出:Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2);
    //类型都是Person,依据字符串和数组的相等运算符比较规则,输出:False

    person2 = person1 with { PhoneNumbers = new string[1] };
    Console.WriteLine(person2);
    //输出:Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2); 
    //PhoneNumbers数组引用不同对象,输出:False

    person2 = person1 with { };
    Console.WriteLine(person1 == person2); 
    //person2是person1的副本,输出:True
}

原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/csharp/csharplang/11948.html

(2)
上一篇 2021年3月11日 06:01
下一篇 2021年3月13日 15:58

相关推荐

  • 1.5C#语言的语句(Statement)

    C#语言的语句往往以 ; 结尾。 1.空语句 空语句只含一个 ; 分号。不执行任何操作,可以在需要语句但不需要执行任何操作的地方使用。 2.代码块(block) 代码块是由在分隔符 { 和 } 内编写的语句组成。当代码块内语句为一条语句时,往往可以省略花括号,如果多条语句时,此时…

    C#语言教程 2021年1月5日
    06140
  • 2.7C#函数成员之事件(Event)

    1.事件委托声明 .NET 类库中的所有事件均基于 EventHandler 委托、EventHandler<TEventArgs> 委托,一般不建议自定义委托。 第一个参数为object类型,表示能够引发事件的类的实例。 第二个参数为从 EventArgs 基类派生的子类型的…

    C#语言教程 2021年2月7日
    03360
  • 3.4C#引用类型之类(Class)

    1.类声明 2.继承 派生类只能有一个直接基类。但是,因为一个基类本身可能继承自另一个类,所以一个类可能会间接继承多个基类。省略基类相当于从 object 类型继承。 派生类会隐式获取基类的所有成员(除了基类的静态构造函数、实例构造函数、析构函数),所以无需在派生类再书写继承过来的基类成员,但您还可…

    C#语言教程 2021年3月4日
    06210

发表回复

登录后才能评论