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来声明一个基于class的record,和原来的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(1, 2);
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 { get; set; }
public int Y { get; set; }
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 { get; set; }
public int Y { get; set; }
private int Z { get; set; }
public Point2()
{
X = -1;
Y = -1;
Z = 0;
}
public override string ToString()
{
return $"{X}_{Y}_{Z}";
}
}
需要注意的是 default 和 new 的差别,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 解读

