大数跨境
0
0

C# 10 新特性 —— struct 更新

C# 10 新特性 —— struct 更新 amazingdotnet
2021-11-14
3
导读:C# 10 中结构体的更新

C# 10 新特性 —— struct 更新

Intro

在 C# 10 中针对结构体(Struct)做了一些优化,我们可以使用 record struct 就像 C# 9 中的 record 一样,也可以定义结构体的无参构造函数,下面来看一下示例吧

Record Struct

在 C# 9 中就有了 record,但是 C# 9 中的 record 是引用类型,是一个 class,而 record struct 是一个结构体,是值类型

使用方式如下:

record struct Point(int X, int Y);

相应的,C# 10 也支持 record class 来声明一个基于 classrecord,和原来的 record 是一样的,比如 record class RecordClassModel(int Id, string Name) 等同于 record RecordClassModel(int Id, string Name)

和 C# 9 中的 record 类似,record struct 也会自动地生成 Equals/GetHashCode 并重写 ==/!= 操作符等,可以使用 with 来修改部分属性创建新对象,另外如果 record struct 定义了 primary constructor 也就是声明类型的时候通过 (int X, int Y); 的方式声明的构造器有参数,则会生成一个隐式的无参构造方法

使用示例如下:

var p = new Point(12);
Console.WriteLine(p);
var p1 = p with { X = 2 };
Console.WriteLine(p1);
Console.WriteLine(new Point());

可以看到即使我们没有显式的声明无参构造方法,也还是会有一个无参构造方法可以调用来初始化,如上面示例的最后一种写法

输出结果如下:

Point { X = 1, Y = 2 }
Point { X = 2, Y = 2 }
Point { X = 0, Y = 0 }

如果使用低版本的 C# 会是什么样的呢,我们可以使用 IL Spy 看一下生成的类型在低版本的实现,我们以 C# 6.0 为例看一下低版本中的代码实现,大致如下:

internal struct Point : IEquatable<Point>
{
 public int X { getset; }

 public int Y { getset; }

 public Point(int X, int Y)
 {
  this.X = X;
  this.Y = Y;
 }

 public override readonly string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("Point");
  stringBuilder.Append(" { ");
  if (PrintMembers(stringBuilder))
  {
   stringBuilder.Append(' ');
  }
  stringBuilder.Append('}');
  return stringBuilder.ToString();
 }

 private readonly bool PrintMembers(StringBuilder builder)
 {
  builder.Append("X = ");
  builder.Append(X.ToString());
  builder.Append(", Y = ");
  builder.Append(Y.ToString());
  return true;
 }

 public static bool operator !=(Point left, Point right)
 {
  return !(left == right);
 }

 public static bool operator ==(Point left, Point right)
 {
  return left.Equals(right);
 }

 public override readonly int GetHashCode()
 {
  return EqualityComparer<int>.Default.GetHashCode(X) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(Y);
 }

 public override readonly bool Equals(object obj)
 {
  return obj is Point && Equals((Point)obj);
 }

 public readonly bool Equals(Point other)
 {
  return EqualityComparer<int>.Default.Equals(X, other.X) && EqualityComparer<int>.Default.Equals(Y, other.Y);
 }

 public readonly void Deconstruct(out int X, out int Y)
 {
  X = this.X;
  Y = this.Y;
 }
}

会自动实现 IEquatable 接口并且会重写 ToString()/Equals/GetHashCode 等方法以及 ==/!= 操作符

如果声明了 primary constructor 也会生成对应的 Deconstruct 方法,可以对比一下和之前 record class 的异同,可以对比这篇介绍 C# 9 新特性 — record 解读

从 C# 7.2 开始,我们可以使用 readonly 来标记结构体,在 C# 10 中我们也可以使用 readonly struct record,但是 record struct 不能使用 ref 修饰符

使用 readonly struct record 声明的结构体,如果使用 Primary Constructor,对应的属性会是 init

比如 readonly record struct Point3(int X, int Y); 对应的属性声明会是这样的

internal readonly struct Point3 : IEquatable<Point3>
{
 public int X { get; init; }

 public int Y { get; init; }

 public Point3(int X, int Y)
 {
  this.X = X;
  this.Y = Y;
 }
 // ...
}

Parameterless Constructor

C# 10 开始支持用户自定义无参构造方法,可以的无参构造方法中加入一些初始化的逻辑,这在低版本是不允许出现的,示例如下:

Console.WriteLine(new Point2().ToString());
Console.WriteLine(default(Point2).ToString());
Console.WriteLine(Activator.CreateInstance<Point2>());

struct Point2
{
    public int X { getset; }

    public int Y { getset; }

    private int Z { getset; }

    public Point2()
    {
        X = -1;
        Y = -1;
        Z = 0;
    }

    public override string ToString()
    {
        return $"{X}_{Y}_{Z}";
    }
}

需要注意的是 defaultnew 的差别,default 是一个结构体的空状态,不会执行无参构造方法,但是 new 是会执行的,通过反射创建对象的时候也会执行构造方法,上面的代码输出结果如下:

-1_-1_0
0_0_0
-1_-1_0

More with expression

除了 record 之外,C# 10 还扩展了 with 表达式的用法,对于普通的结构体和匿名对象也可以使用 with 来修改部分属性,示例如下(这里的 Point2 就是上面示例中定义的结构体):

// struct with expression
Console.WriteLine((new Point2() with { X = 2 }).ToString());
Console.WriteLine();

// Anoymous object with expression
var obj = new
{
    X = 2,
    Y = 2
};
Console.WriteLine(JsonSerializer.Serialize(obj));
Console.WriteLine(JsonSerializer.Serialize(obj with { X = 3, Y = 3 }));

输出结果如下:

2_-1_0

{"X":2,"Y":2}
{"X":3,"Y":3}

With 的时候只能对 public 的成员进行操作,上面的 Z 是 private 的,所以在 with 表达式中是不能指定的

来看一个匿名对象 with 表达式的反编译结果,主要是重新创建了一个新对象

var obj = new
{
    X = 2,
    Y = 2
};
var obj1 = obj with { X = 3 };

More

细心的童鞋可能会发现和 record class 相比,record struct 是没有 Clone 方法的,因为 struct 不需要,自带 Clone 功能,但是 record struct 也是不允许声明 Clone 成员方法的,所有的 record 都不允许声明 Clone 成员的,可以看作是个约定

References

  • https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/record-structs?WT.mc_id=DT-MVP-5004222
  • https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/parameterless-struct-constructors?WT.mc_id=DT-MVP-5004222
  • https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10?WT.mc_id=DT-MVP-5004222
  • https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct?WT.mc_id=DT-MVP-5004222
  • https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md
  • https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp10Sample/StructSample.cs
  • C# 9 新特性 — record 解读


【声明】内容源于网络
0
0
amazingdotnet
dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
内容 539
粉丝 0
amazingdotnet dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
总阅读631
粉丝0
内容539