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 解读




