Advanced C# Tips: Prefer Structs for Immutable Data
Advanced C# Tips: Prefer Structs for Immutable Data
高级 C# 技巧:对于不可变数据,优先使用结构体
Before diving into topic, I want to begin with clarifying the terminology. Immutable data refers to data whose state cannot be modified after it has been created. Once an immutable object is created, its internal state remains constant throughout its lifetime. Any attempt to modify the object's state results in the creation of a new object with the updated state, leaving the original object unchanged.
在深入主题之前,我想先澄清一下术语。不可变数据(Immutable data)是指创建后其状态不能再被修改的数据。一旦一个不可变对象被创建,其内部状态在其整个生命周期中保持不变。任何尝试修改该对象状态的操作都会导致创建一个具有更新状态的新对象,而原始对象保持不变。
In C#, you can define your data using either classes or structs. The choice between a class and a struct can influence the behaviour and performance of your application. Classes are reference types and structs are value types. We also have records introduced in C# 9.0 and specifically for data. It deserves a separate post which I am going to write, but let's keep this article's scope limited to class and struct. For records in C# see Microsoft's reference. The distinction between class and struct lies in how they are handled in memory. When you create a class, you're making an object in the heap, and you work with references to that object. If you pass this object to a method, you're passing a reference to it, not the actual object. This means if the object is changed in the method, it changes everywhere that reference is used.
在 C# 中,你可以使用类(class)或结构体(struct)来定义数据。类和结构体之间的选择会影响应用程序的行为和性能。类是引用类型,结构体是值类型。我们还有在 C# 9.0 中引入的、专门用于数据的记录(record)。它值得单独写一篇文章,我将会写,但让我们把本文的范围限定在类和结构体上。关于 C# 中的记录,请参阅微软的参考文档。类与结构体的区别在于它们在内存中的处理方式。当你创建一个类时,你是在堆(heap)中创建了一个对象,并且你使用的是指向该对象的引用。如果你将这个对象传递给一个方法,你传递的是对它的引用,而不是实际的对象。这意味着如果该对象在方法中被更改,那么所有使用该引用的地方都会受到影响。
When you create a struct, it is stored directly in the location where you declare it. It is created on the stack and not on the heap. When you pass a struct to a method, you are passing the whole thing, not just a reference. This makes copying more expensive, but since there's no reference, it's much faster to access since you're working directly with the data.
当你创建一个结构体时,它直接存储在声明它的位置。它是在栈(stack)上创建的,而不是在堆上。当你将一个结构体传递给一个方法时,你传递的是整个对象,而不仅仅是一个引用。这使得复制更加昂贵,但由于没有引用,访问速度要快得多,因为你是直接操作数据。
Using structs for immutable data is efficient. Without the overhead of dynamic memory allocation (which classes require since they are reference types and live on the heap, more like a filing cabinet than the top of your desk), structs can be more performant. They are created and dealt with right at the place they are used, without the need for the garbage collector to clean them up later, which saves time and resources.
使用结构体来表示不可变数据是高效的。没有动态内存分配的开销(类需要动态内存分配,因为它们是引用类型,并且存在于堆上,更像是文件柜而不是你桌面上的物品),结构体可以具有更高的性能。它们在使用的原地就能被创建和处理,无需垃圾回收器后续来清理它们,这节省了时间和资源。
Let's consider an example of a point on a 2D plane:
让我们考虑一个二维平面上点的示例:
public struct Point2D
{
public readonly double X;
public readonly double Y;
public Point2D(double x, double y)
{
X = x;
Y = y;
}
}
In this case, Point2D is a struct with X and Y coordinates. Once a Point2D is created, X and Y don't change; they are immutable. This is an ideal scenario for using a struct instead of a class.
在这个例子中,Point2D 是一个带有 X 和 Y 坐标的结构体。一旦 Point2D 被创建,X 和 Y 就不会改变;它们是不可变的。这是一个使用结构体而非类的理想场景。
Studies and benchmarks in performance-critical applications often show that structs can offer significant memory and speed advantages. For example, a test might reveal that an operation using structs completes faster and uses less memory than the same operation using classes.
性能关键型应用中的研究和基准测试通常表明,结构体可以提供显著的内存和速度优势。例如,测试可能会揭示,使用结构体的操作比使用类的同样操作完成得更快,内存使用更少。
However, it is important to use structs wisely. Structs in C# can be used to represent immutable data, but there are drawbacks. When structs are passed as objects, they may need to be boxed, leading to performance overhead. Additionally, structs are allocated on the stack, which can cause memory issues if they contain a lot of data. Since structs are copied when passed as arguments or stored in collections, memory usage can increase. Unlike classes, structs cannot inherit from other structs or classes, limiting their flexibility. Also, there's no compile-time enforcement of immutability for struct fields, which could lead to unintended mutations. Considering these limitations, using immutable classes may be preferable for managing immutable data in C#.
然而,明智地使用结构体非常重要。C# 中的结构体可以用来表示不可变数据,但也存在一些缺点。当结构体作为对象传递时,它们可能需要装箱(boxing),从而导致性能开销。此外,结构体在栈上分配,如果包含大量数据,可能引发内存问题。由于结构体在作为参数传递或存储在集合中时会被复制,内存使用可能会增加。与类不同,结构体不能继承自其他结构体或类,这限制了它们的灵活性。而且,结构体字段没有编译时的不可变性强制约束,这可能导致意外的修改。考虑到这些限制,使用不可变类来管理 C# 中的不可变数据可能更为可取。
Suleyman Cabir Ataman, PhD
This entry is part 9 of 17 in the series Advanced C# Tips.
本文是 Advanced C# Tips(高级 C# 技巧) 系列 17 篇中的第 9 篇。

浙公网安备 33010602011771号