何时应该使用结构体而不是类?
在Bill Wagner的书《effective c#》中,他提到了何时应该使用结构体而不是类。他通过以下原则得出结论:
1. 类型的主要责任是数据存储吗?
2. 它的公共接口是否完全由访问或修改其数据成员的属性定义?
3. 您确定您的类型永远不会有子类吗?
4. 您确定您的类型永远不会以多态方式对待吗?
如果对这4个问题的回答都是“是”,则应该使用结构体。否则,使用类。
那么...数据传输对象(DTO)应该是结构体吗?
我认为是的,如果满足上述4个标准。为什么数据传输对象需要以特定方式处理取决于您的情况。在一个项目中,我们的DTO中有常见的审计字段,因此编写了一个基础DTO,其他DTO从该基础DTO继承。
根据DTO的定义,如果您不从基类继承任何内容(这在DTO中很常见),则可以将其定义为结构体。
除了(2)之外,其他原则都非常好。需要看他的论证才能知道他在(2)中具体指的是什么,以及原因是什么。
关于这个问题的详细解释,请阅读他的书。抄袭书中的大部分内容是不公平的。
当应该使用结构体而不是类时会出现以下原因:
1. 结构体在逻辑上表示一个单一的值,类似于原始类型(整数、双精度等)。
2. 结构体的实例大小小于16个字节。
3. 结构体是不可变的。
4. 结构体不需要频繁进行装箱。
然而,有人对"不可变"这个条件的原因表示疑惑,为什么这是必要的呢?有人认为,这是因为如果结构体是不可变的,那么它具有值语义而不是引用语义就没有关系。只有在复制对象/结构体后对其进行更改时,这种区别才有意义。
在某些情况下,系统会临时复制一个结构体,然后允许将该副本按引用传递给修改它的代码;由于临时副本将被丢弃,所以更改将丢失。这个问题在结构体具有改变它的方法时尤为严重。然而,有人坚决不同意将可变性作为使某个类型成为类的原因,因为尽管c#和vb.net存在一些缺陷,但可变的结构体提供了无法通过其他方式实现的有用语义;没有语义上的理由优先选择不可变的结构体而不是类。
有人更喜欢Scott Meyers的原则。他明确偏离了.NET文档,因为他认为类型的使用是比类型的大小更好的因素。
有人对16字节的大小是否适用于64位代码表示疑惑。关于16字节的数字的来源,他们并不完全确定。
在设计JIT编译器时,微软决定对大小为16字节或更小的结构体进行代码优化;这意味着复制17字节的结构体可能比复制16字节的结构体慢得多。他们认为微软不太可能将这样的优化扩展到更大的结构体上,但重要的是要注意,虽然复制17字节的结构体可能比复制16字节的结构体慢,但有很多情况下,大型结构体可能比大型类对象更高效,并且结构体的相对优势随着结构体的大小增加而增加。
将相同的用法模式应用于大型结构体与应用于类的情况相比,可能会导致低效的代码,但正确的解决方法通常不是用类替换结构体,而是更有效地使用结构体;最重要的是,应避免按值传递或返回结构体。在合理的情况下,将一个具有4000个字段的结构体作为ref参数传递给一个修改字段的方法,比按值传递具有4个字段的结构体然后返回修改后的版本更便宜。
关于大型结构体何时更高效于大型类实例的"many cases"是什么?我只能想到一种可能,即必须强制执行不可变性,因此在执行任何操作时,"class"方法最终需要创建一个新的实例...我从未遇到过这种情况,但我很想知道它在何时发生。
我知道这是一个旧帖子,但是结构体被建议为不可变,因为它们通常(记住接口引用)是值类型对象,而值是被复制而不是更新的。虽然可以指向结构体并更改现有值;但是结构体仍然是复制而不是就地更新,就像类的工作方式一样。换句话说,这更安全,更符合现有结构体(例如int、char、DateTime等)的工作方式。
当应该使用结构体而不是类时?
我很惊讶之前的回答中没有提到我认为最关键的一点:
当我想要一个没有身份的类型时,我使用结构体。例如一个三维点:
public struct ThreeDimensionalPoint
{
public readonly int X, Y, Z;
public ThreeDimensionalPoint(int x, int y, int z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public override string ToString()
{
return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")";
}
public override int GetHashCode()
{
return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2);
}
public override bool Equals(object obj)
{
if (!(obj is ThreeDimensionalPoint))
return false;
ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj;
return this == other;
}
public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
{
return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z;
}
public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
{
return !(p1 == p2);
}
}
如果你有两个这个结构体的实例,你不关心它们是不是在内存中是一个单独的数据还是两个。你只关心它们所持有的值。
使用结构体的一个有趣的原因。我曾经创建过具有类似于你所展示的GetHashCode和Equals方法的类,但是我总是不得不小心不要改变这些实例,如果我把它们用作字典的键。如果我将它们定义为结构体可能会更安全。(因为然后键将是在结构体成为字典键的那一刻字段的副本,所以如果我以后更改了原始值,键将保持不变。)
在你的例子中没问题,因为你只有12个字节,但要记住,如果该结构体中有很多字段超过16个字节,你必须考虑使用类并重写GetHashCode和Equals方法。
在DDD中的值类型并不意味着你在C#中一定要使用值类型。