是否有实际的理由在Microsoft .NET 2.0 / 3.5中使用结构而不是某些类?
“结构和类之间有什么区别?” - 这可能是关于“.NET开发人员”职位空缺的最热门问题。面试官认为正确的唯一答案是“结构在堆栈上分配,类在堆上分配”,并且没有进一步的问题。
一些谷歌搜索显示:
a)结构有很多限制,与类相比没有额外的能力 b)堆栈(同样如此 结构)可以在非常特殊条件下更快,包括:
- 数据块大小减去16字节
- 没有广泛的装箱/拆箱
- 结构的成员几乎是不可改变的
- 整套数据不大(否则我们得到堆栈溢出)
(如果错误或未满,请更正/添加到此列表中)
据我所知,大多数典型的商业项目(ERM,会计,银行解决方案等)甚至没有定义单一结构,所有自定义数据类型都被定义为类。这种方法有什么不对或至少是不完美的吗?
注意:问题是关于普通商业应用程序,请不要列出游戏开发,实时动画,向后兼容性(COM / Interop),非托管代码等“异常”案例 - 这些答案已经出现在这个类似的问题中:
答案 0 :(得分:12)
据我所知,大多数典型的商业项目(ERM,会计,银行解决方案等)甚至没有定义单一结构,所有自定义数据类型都被定义为类。这种方法有什么不对或至少是不完美的吗?
没有!一切都完全正确。您的一般规则应该是默认情况下始终使用对象。毕竟我们讨论的是面向对象的编程,而不是面向结构的编程(结构本身缺少一些OO原则,如继承和抽象)。
然而结构有时更好如果:
答案 1 :(得分:8)
IMO最重要的用例是小型复合实体的大型数组。想象一个包含10 ^ 6个复数的数组。或者包含1000x1000 24位RGB值的2d数组。使用struct而不是类可以在这样的情况下产生巨大的差异。
编辑: 澄清一下:假设你有一个结构
struct RGB
{
public byte R,G,B;
}
如果声明一个1000x1000 RGB值的数组,则此数组将占用3 MB的内存,因为值类型以内联方式存储。
如果使用类而不是结构,则数组将包含1000000个引用。仅此一项就需要4或8 MB(在64位机器上)内存。如果您使用单独的对象初始化所有项目,因此您可以单独修改这些值,您可以在托管堆上旋转1000000个对象以保持GC忙碌。每个对象都有2个引用的开销(IIRC),即对象将使用11/19 MB的内存。总的来说,它的内存是简单结构版本的5倍。
答案 2 :(得分:6)
堆栈分配值类型的一个优点是它们是线程的本地。这意味着它们本质上是线程安全的。对于堆上的对象,这不能说。
这当然假设我们正在讨论安全的托管代码。
答案 3 :(得分:3)
与类的另一个区别在于,当您将结构实例分配给变量时,您不仅要复制引用,还要复制整个结构。因此,如果你修改其中一个实例(你不应该,因为结构实例是不可变的),另一个实例不会被修改。
答案 4 :(得分:1)
到目前为止所有的好答案......我只需要补充一点,根据定义,值类型不可为空,因此在你不想被创建类的新实例的情况下使用它们是一个很好的候选者。并将其分配给字段,例如......
struct Aggregate1
{
int A;
}
struct Aggregate2
{
Aggregate1 A;
Aggregate1 B;
}
请注意,如果Aggregate1
是一个类,那么您必须手动初始化Aggregate2中的字段...
Aggregate2 ag2 = new Aggregate2();
ag2.A = new Aggregate1();
ag2.B = new Aggregate1();
这显然不是必需的,只要Aggregate1是一个结构...这可能证明在创建类/结构层次结构时有用,为了明确目的,使用XmlSerializer
许多看似序列化/反序列化在这种情况下,仅使用结构就会消除神秘异常。
答案 5 :(得分:1)
如果类型的目的是将小的固定独立值集合与管道磁带绑定在一起(例如,点的坐标,键和枚举字典条目的关联值,六项2d变换矩阵,从效率和语义两个角度来看,最佳表示可能是一个可变的暴露场结构。请注意,这表示一个非常不同的使用场景,其中struct表示单个统一的概念(例如Decimal
或DateTime
),Microsoft对何时使用结构的建议提供的建议仅适用到后一个。微软描述的“不可变”结构的风格只适用于表示单一的统一概念;如果一个人需要代表一个小的固定的独立值集合,那么正确的选择不是一个不可变类(它提供较差的性能),也不是一个可变类(在很多情况下会提供不正确的语义),而是一个公开的字段结构(当正确使用时 - 提供卓越的语义和性能)。例如,如果其中一个结构MyTransform
包含一个二维变换矩阵,则可以使用如下方法:
static void Offset(ref it, double x, double y)
{
it.dx += x;
it.dy += y;
}
比
更快更清晰static void Offset(ref it, double x, double y)
{
it = new Transform2d(it.xx, int.xy, it.yx, it.yy, it.dx+x, it.dy+y);
}
或
Transform2d Offset(double dx, double dy)
{
it = new Transform2d(xx, xy, yx, yy, dx+x, dy+y);
}
知道dx和dy是Transform2d
的字段就足以知道第一种方法修改了这些字段并且没有其他副作用。相反,要知道其他方法的作用,就必须检查构造函数的代码。
答案 6 :(得分:0)
有一些优秀的答案涉及使用结构与类的实用性,反之亦然,但我认为你对结构不可变的原始评论是一个很好的论据,为什么类更常用于高级LOB应用程序的级别设计。
在域驱动设计http://www.infoq.com/minibooks/domain-driven-design-quickly中,实体/类和值对象/结构之间存在某种程度的并行关系。 DDD中的实体是业务域中的项目,我们需要使用标识符跟踪其身份,例如, CustomerId,ProductId等。值对象是我们可能感兴趣的值的项目,但我们不会使用标识符(如Price或OrderDate)跟踪其标识。实体在DDD中是可变的,除了它们的标识字段,而值对象没有标识。
因此,在对典型的业务实体进行建模时,通常会设计一个类以及跟踪标识的标识属性业务对象从持久性存储区往返再次返回。虽然在运行时我们可能会更改业务对象实例上的所有属性值,但只要标识符是不可变的,就会保留实体的标识。对于与Money或Time对应的业务概念,结构很自然,因为即使每当我们执行计算时都会创建一个新实例,也没关系,因为我们没有跟踪身份,只存储一个值。
答案 7 :(得分:-3)
有时,你只想在组件之间传输数据,然后struct比class更好。例如数据传输对象(DTO)仅承载数据。