关于何时使用struct vs.class的经验法则是什么?我正在考虑这些术语的C#定义,但是如果您的语言具有类似的概念,我也想听听您的意见。某些东西非常简单,应该是一个值类型,例如PhoneNumber或类似的东西。但这似乎是一个相对较小的用途,我希望有更多有趣的用例。
#1 楼
遵循的一般规则是,结构应该是相关属性的小型,简单(一级)集合,一旦创建,它们就不可变。C#的优点是,结构和类在声明中除了define关键字外没有显着区别;因此,如果您觉得需要将结构“升级”到类中,或者相反,将类“降级”到结构中,则更改关键字通常很简单(还有其他一些陷阱;结构不能派生来自任何其他类或结构类型,它们不能显式定义默认的无参数构造函数。)
我之所以说“大部分”,是因为了解结构最重要的是,值类型,将它们像类(引用类型)一样对待可能会导致痛苦的一半。特别是,使结构的属性可变可以导致意外的行为。
例如,假设您有一个具有两个属性A和B的类SimpleClass。您实例化此类的副本,初始化A和B,然后将实例传递给另一个方法。该方法进一步修改了A和B。回到调用函数(创建实例的函数)中,实例的A和B将具有被调用方法赋予它们的值。
现在,您使它成为一个结构。该属性仍然可变。您可以使用与以前相同的语法执行相同的操作,但是现在,调用该方法之后,实例中不再存在A和B的新值。发生了什么?好了,您的类现在是一个结构,这意味着它是一个值类型。如果将值类型传递给方法,则默认值(不带out或ref关键字)将传递“按值”;创建实例的浅表副本以供该方法使用,然后在方法完成时销毁该副本,而保留初始实例的完整性。
如果将引用类型作为结构的成员,这将变得更加令人困惑(这是不允许的,但实际上在所有情况下都是极差的做法);不会克隆该类(仅复制结构的引用),因此对结构的更改不会影响原始对象,但是对结构的子类的更改将影响调用代码中的实例。这很容易使可变结构处于非常不一致的状态,从而可能导致距实际问题很远的地方出错。 ;允许使用者仅在构造对象时指定属性的值,而从不提供任何更改该实例的值的方法。只读字段或仅获取属性是规则。如果消费者想要更改值,则他们可以基于旧对象的值创建一个新对象,并带有他们想要的更改,或者可以调用将执行相同操作的方法。这迫使他们将您的结构的单个实例视为一个概念上的“值”,该值与其他结构不可分割且有区别(但可能等同)。如果他们对您类型存储的“值”执行运算,则会获得一个新的“值”,该值不同于其初始值,但仍具有可比性和/或语义上相等。
举一个很好的例子,看看DateTime类型。您不能直接分配DateTime实例的任何字段。您必须创建一个新实例,或者在现有实例上调用将产生一个新实例的方法。这是因为日期和时间是“值”,例如数字5,更改数字5会导致新值不是5。仅仅因为5 + 1 = 6并不意味着5现在是6因为您添加了1。 DateTimes的工作方式相同;如果增加一分钟,则12:00不会“成为” 12:01,而是获得一个新的值12:01,该值不同于12:00。如果这是您所用类型的逻辑状态(.NET中未内置的良好概念示例,例如Money,Distance,Weight和UOM的其他数量,其中操作必须考虑值的所有部分),然后使用结构并进行相应的设计。在大多数情况下,对象的子项应该独立可变,请使用类。
评论
好答案!我想指出的是,它是“域驱动开发”方法和书籍的阅读者,这似乎与您关于为什么应该根据实际尝试实现的概念使用类或结构的观点有关
–马克西姆·波普拉夫科(Maxim Popravko)
2011年7月12日在18:31
值得一提的只是一点细节:您不能在结构上定义自己的默认构造函数。您必须将所有内容初始化为其默认值。通常,这是次要的,特别是在适合作为结构体的类上。但这意味着将类更改为结构可能意味着不仅仅更改关键字。
– Laurent Bourgault-Roy
2012年12月20日在22:04
@ LaurentBourgault-Roy-是的。还有其他陷阱。例如,结构不能具有继承层次结构。它们可以实现接口,但不能派生自System.ValueType以外的任何对象(暗含它们的结构)。
– KeithS
2012年12月20日在22:15
作为一个JavaScript开发人员,我必须问两件事。对象文字与结构的典型用法有何不同?为什么结构不可变很重要?
–埃里克·雷彭(Erik Reppen)
2012年12月21日下午5:50
@ErikReppen:回答您的两个问题:将结构传递给函数时,它按值传递。对于类,您正在传递对该类的引用(仍按值)。埃里克·利珀特(Eric Lippert)讨论了为什么会出现此问题:blogs.msdn.com/b/ericlippert/archive/2008/05/14/…。 KeithS的最后一段也涵盖了这些问题。
–布赖恩
2012-12-21 14:35
#2 楼
答案:“对于纯数据结构使用结构,对带有操作的对象使用类”绝对是错误的IMO。如果一个结构拥有大量的属性,那么一个类几乎总是更合适。从效率的角度来看,Microsoft经常说,如果您的类型大于16个字节,则应该是一个类。 -net-struct小于16个字节评论
绝对。如果可以的话,我会投票赞成。我不知道这个想法从何而来:结构是针对数据的,而对象不是。 C#中的结构只是一种用于在堆栈上分配的机制。传递整个结构的副本,而不仅仅是对象指针的副本。
–mike30
2012-12-20 17:30
@mike-不一定在堆栈上分配C#结构。
–李
2012-12-20 18:37
@mike“结构用于数据”的想法可能来自多年的C编程习惯。 C没有类,其结构是仅数据容器。
–科纳米曼
2012年12月21日上午11:07
显然,至少有3位C程序员(被投票赞成)已经阅读了Michael K的答案;-)
– bytedev
13年1月10日在16:01
#3 楼
class
和struct
之间最重要的区别是在以下情况下发生的情况:如果thing1.somePropertyOrField
是一个结构,而Thing
是一个公开的公共字段,则对象somePropertyOrField
和thing1
将彼此“分离”,因此后面的语句不会影响thing2
。如果thing1
是一个类,则Thing
和thing1
将彼此连接,因此后面的语句将写thing2
。在前一种语义更有意义的情况下,应该使用一个结构,而在后一种语义更有意义的情况下,应该使用一个类。请注意,尽管有人建议使某事物可变为参数是有利于它成为类的,我建议相反的说法是正确的:如果为了保存某些数据而存在的某物体将可变,并且是否附加实例则不明显对于任何事物,事物都应该是一个结构体(可能具有暴露的字段),以使实例没有附加到其他事物上。
例如,考虑以下语句:
Thing thing1 = new Thing(); thing1.somePropertyOrField = 5; Thing thing2 = thing1; thing2.somePropertyOrField = 9;
第二条语句会改变存储在
thing1.somePropertyOrField
中的信息吗?如果myPeople
是一个暴露场结构,它就不会,而且事实并非如此,这显然是因为它是一个暴露场结构;如果Person
是一个结构并且希望更新Person
,则显然必须做类似myPeople
的操作。但是,如果myPeople.UpdatePerson("123-45-6789", somePerson)
是一类,则确定上面的代码是否永远不会更新Person
的内容,始终更新它,或者有时更新它可能要困难得多。关于结构应该是“不变的”的观点,我总体上不同意。对于“不可变的”结构,存在有效的使用情况(在构造函数中强制执行不变式),但要求在其任何部分更改笨拙,浪费且更容易引起错误的情况下,必须重写整个结构,而不仅仅是暴露字段直接。例如,考虑一个
MyPeople
结构,其字段包括PhoneNumber
和AreaCode
,并且假设其中一个具有Exchange
。以下内容的效果应该很清楚:如果List<PhoneNumber>
是所谓的“不可变”结构,则有必要为每个字段提供PhoneNumber
方法,该方法将返回一个新的结构实例,该实例在指定字段中保留传入的值,否则它将对于像上面的代码来说,了解结构中的每个字段都是必需的。 顺便说一句,至少在两种情况下,结构可能合理地持有对可变类型的引用。
结构的语义表明它是存储有关对象的标识,而不是作为保存对象属性的快捷方式。例如,“ KeyValuePair”将保存某些按钮的标识,但不会保留有关这些按钮的位置,突出显示状态等的持久性信息。
该结构知道它保存了仅引用该对象,没有其他人会获得引用,并且对该对象执行的任何突变都将在对该对象的引用存储到任何地方之前进行。
在在前一种情况下,对象的IDENTITY将是不可变的;在第二种方法中,嵌套对象的类可能不强制不变性,但是保存引用的结构将实现。
评论
最初阅读支持此答案的几年后,我又一次换了零钱来到这里,希望我能两次投票赞成。使用ORM和以结构表示的实体感觉更加透明,线程安全和无害。不过,尽管如此,我仍然会坚持不变性,其副作用会更加有限
– Ivaylo Slavov
20 May 28 '18:26
@IvayloSlavov:假冒的“不变性”买了什么?如果结构方法链接到任何外部方法,并且那些方法改变了调用该方法的结构的值,则尽管该方法被认为是“不可变的”,但该方法中该方法的值仍将发生变化。
–超级猫
20 May 28 '19:37
@IvayloSlavov:如果一个人使用的是“不可变的”结构,那么一个人应该如何编写需要修改两个字段并剩下其余部分的代码?如果使用公开字段结构并添加新字段,则更新其他字段的代码将使新字段保持不变,无论它是否意识到它们。此外,如果执行myThing.x.y.z.someMethod()时链中的每个项目都是暴露字段结构,则代码将比链中任何部分为只读的情况更有效。
–超级猫
20-05-28在19:46
@IvayloSlavov:我想描述我的哲学的最好方法是说,人们通常应该识别并公开.NET中的三种对象类型:类对象,具有超出外部代码直接作用范围的行为的结构(非平凡的)以及琐碎的结构。在实践中,具有非平凡行为的结构应该是不变的,但是没有有用的非平凡行为的结构应该是平凡的。由于不具有外部代码无法做的任何行为的不变结构将是无用的,因此这意味着可变的琐碎结构必须是可变的。
–超级猫
20-05-29在16:04
@IvayloSlavov:换句话说,真正的问题不应该是结构是否应该是“可变的”,而应该是不平凡的。如果有一个结构不平凡的原因,那通常意味着它应该是不可变的。但是,如果没有理由使结构不平凡,那么它应该是平凡的,这意味着(如果是平凡的)它必须是可变的(否则就必须是平凡的)。
–超级猫
20-05-29在16:07
#4 楼
我倾向于仅在需要一种方法时才使用结构,从而使类显得“过重”。因此,有时我将结构视为轻量级对象。注意:这并不总是可行,也不总是最好的做法,因此它确实取决于情况! :http://msdn.microsoft.com/zh-cn/library/ms229017.aspx此链接验证了我上面提到的内容,结构仅应用于容纳少量数据。
评论
在另一个站点(即使该站点是Stack Overflow)上的问题也不会被视为重复项。请扩展您的答案以包括实际答案,而不仅仅是指向其他地方的链接。如果链接发生更改,那么您现在写的答案将毫无用处,我们希望保留信息并使程序员尽可能独立。谢谢!
–亚当·李尔♦
2011年7月12日在17:27
@Anna Lear感谢您让我知道,从现在开始请记住这一点!
–疯狂
2011年7月12日在17:37
-1“我倾向于仅在需要一种方法时才使用结构,因此使类看起来过于“繁重”。” ...重吗?那个的真实意义是什么? ...而您是否真的在说,因为cos只有一种方法,那么它可能应该是一种结构?
– bytedev
16 Dec 19 '14:03
#5 楼
对于纯数据结构,请使用结构,对于具有操作的对象,请使用类。这很明显所有结构都是数据的容器。如果您的结构具有以任何更复杂的方式修改结构的操作,请使用类。一个类也可以通过方法的参数与其他类进行交互。
将其视为砖头和飞机之间的区别。砖是结构。它具有长度,宽度和高度。它做不了什么。一架飞机可能会进行多种改变操作的操作,例如移动控制面和改变发动机推力。最好还是上课。
评论
在C#中,“对纯数据结构使用结构,对带有操作的对象使用类”是胡说八道。请参阅下面的答案。
– bytedev
2014年3月25日在16:38
“将结构用于纯数据结构,将类用于具有操作的对象。”对于C / C ++,而不是C#,这是正确的
–taktak004
17年8月3日在17:39
评论
可能重复:stackoverflow.com/questions/6267957/struct-vs-class@Frustrated无法将问题标记为另一个站点上某事物的重复,尽管它肯定是相关的。
@Anna Lear:我知道,我投票决定不迁移为重复副本。迁移后,可以将其作为副本正确关闭。
@沮丧我认为不需要迁移。
这似乎是一个更适合P.SE与SO的问题。这就是为什么我在这里问它。