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

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

相关推荐

  • 3.6C#引用类型之字符串(String)

    1.字符串声明 2.获取字符串长度 3.访问字符串 4.字符串连接 可使用 + 运算符 或 += 运算符连接字符串。 5.字符串内插(Interpolation) 若要将字符串标识为字符串内插(Interpolation),可在该字符串 ” 左引号前面加上 $ 美元符号( $ 和 ” 之间不能有任何…

    C#语言教程 2021年3月6日
    03010
  • 5.2C#异步编程场景

    C#异步编程场景分为 I/O 绑定(例如从网络请求数据、访问数据库或读取和写入到文件系统)和 CPU 绑定(例如执行成本高昂的计算)。 I/O 绑定场景:从 Web 服务下载数据 对于 I/O 绑定代码,等待一个在 async 方法中返回 Task 或 Task<T> 的操作。 请使用 async 和 await…

    C#语言教程 2021年5月2日
    01000
  • 1.7C#语言的异常(Exception)语句

    异常类派生自System.Exception类。 throw 使用 throw 关键字,程序可以显式生成异常。 e 是一个派生自 System.Exception类 的异常类的实例。 try-catch try…catch 语句用于捕获在代码块执行…

    C#语言教程 2021年1月7日
    02410

发表评论

登录后才能评论