您何时使用结构与类的经验法则是什么?我正在考虑这些术语的C#定义,但是如果您的语言具有类似的概念,我也想听听您的意见。
我倾向于对几乎所有东西使用类,并且仅在某些东西非常简单并且应该是值类型(例如PhoneNumber或类似的东西)时才使用structs。但这似乎是一个相对较小的用途,我希望有更多有趣的用例。
您何时使用结构与类的经验法则是什么?我正在考虑这些术语的C#定义,但是如果您的语言具有类似的概念,我也想听听您的意见。
我倾向于对几乎所有东西使用类,并且仅在某些东西非常简单并且应该是值类型(例如PhoneNumber或类似的东西)时才使用structs。但这似乎是一个相对较小的用途,我希望有更多有趣的用例。
Answers:
遵循的一般规则是,结构应该是相关属性的小型,简单(一级)集合,一旦创建就不可变。对于其他任何事情,请使用一个类。
C#的优点在于,结构和类在声明上除了define关键字外没有其他明显的区别;因此,如果您觉得需要将一个结构“升级”到一个类,或者相反地将一个类“降级”到一个结构,则更改关键字通常是一个简单的问题(还有一些其他陷阱;结构不能派生)从任何其他类或结构类型,并且它们不能显式定义默认的无参数构造函数)。
我说“主要”,是因为了解结构最重要的事情是,因为它们是值类型,所以将它们像类(引用类型)一样对待可能会导致痛苦的一半。特别是,使结构的属性可变可以导致意外的行为。
例如,假设您有一个具有两个属性A和B的类SimpleClass。您实例化该类的副本,初始化A和B,然后将该实例传递给另一个方法。该方法进一步修改了A和B。回到调用函数(创建实例的函数)中,实例的A和B将具有被调用方法赋予它们的值。
现在,您将其设为结构。该属性仍然可变。您可以使用与以前相同的语法执行相同的操作,但是现在,调用该方法之后,实例中不再存在A和B的新值。发生了什么?好了,您的类现在是一个结构,这意味着它是一个值类型。如果将值类型传递给方法,则默认值(不带out或ref关键字)将传递“按值”;创建实例的浅表副本以供该方法使用,然后在完成该方法时将其破坏,而保留初始实例的完整性。
这变得更加混乱,如果你有一个引用类型作为结构的成员(不禁止的,但极其几乎在所有情况下不好的做法); 不会克隆该类(仅复制结构的引用),因此对结构的更改不会影响原始对象,但是对结构的子类的更改将影响调用代码中的实例。这很容易使可变结构处于非常不一致的状态,这可能导致错误,而真正的问题就在很远的地方。
因此,实际上C#上的每一个权威机构都说总是使您的结构不变。允许使用者仅在构造对象时指定属性的值,而从不提供任何更改实例值的方法。只读字段或仅获取属性是规则。如果消费者想要更改值,则他们可以基于旧对象的值创建一个新对象,并带有他们想要的更改,或者可以调用将执行相同操作的方法。这迫使他们将您的结构的单个实例视为一个概念上的“值”,该值是不可分割的,并且与所有其他实例(但可能等同)相同。如果他们对您类型存储的“值”执行运算,则会得到一个不同于其初始值的新“值”,
举一个很好的例子,看看DateTime类型。您不能直接分配DateTime实例的任何字段。您必须创建一个新实例,或者在现有实例上调用将产生一个新实例的方法。这是因为日期和时间是“值”,例如数字5,更改数字5会导致新值不是5。仅仅因为5 + 1 = 6并不意味着5现在是6因为您添加了1。DateTimes的工作方式相同;如果您增加一分钟,则12:00不会“成为” 12:01,而是获得一个不同于12:00的新值12:01。如果这是您所用类型的逻辑状态(.NET中未内置的良好概念性示例,例如Money,Distance,Weight和UOM的其他数量,其中操作必须考虑值的所有部分),然后使用结构并进行相应的设计。在大多数其他情况下,对象的子项应独立可变,请使用类。
答案:“对于纯数据结构使用结构,对带有操作的对象使用类”绝对是错误的IMO。如果一个结构拥有大量的属性,那么一个类几乎总是更合适。从效率的角度来看,Microsoft经常说,如果您的类型大于16字节,则应该是一个类。
https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes
a class
和a 之间最重要的区别struct
是在以下情况下会发生什么:
事物thing1 = new Thing(); something1.somePropertyOrField = 5; 事物thing2 =事物1; something2.somePropertyOrField = 9;
最后一条语句的作用是thing1.somePropertyOrField
什么?如果Thing
一个struct且somePropertyOrField
是一个公开的公共字段,则对象thing1
和thing2
将彼此“分离”,因此后一种语句将不受影响thing1
。如果Thing
是类,则thing1
和thing2
将彼此连接,因此后一个语句将写入thing1.somePropertyOrField
。在前一种语义更有意义的情况下,应该使用一个结构,而在后一种语义更有意义的情况下,应该使用一个类。
请注意,尽管有些人建议使某事物变得可变的愿望是支持它成为类的一个论据,但我建议反之亦然:如果为了保存某些数据而存在的某物将是可变的,并且如果不清楚是否将实例附加到任何事物,则该事物应该是一个结构(可能具有暴露的字段),以明确实例未附加到任何其他事物。
例如,考虑以下语句:
人somePerson = myPeople.GetPerson(“ 123-45-6789”); somePerson.Name =“玛丽·约翰逊”; //曾经是“玛丽·史密斯”
第二条语句会更改存储在其中的信息myPeople
吗?如果Person
是一个暴露场结构,它就不会,而且事实并非如此,这显然是因为它是一个暴露场结构;如果Person
是一个结构并且希望更新myPeople
,那么显然必须做一些事情myPeople.UpdatePerson("123-45-6789", somePerson)
。Person
但是,如果是类,则可能很难确定以上代码是从不更新的内容MyPeople
,始终更新它,还是有时对其进行更新。
关于结构应该是“不变的”的观点,我总体上不同意。对于“不可变的”结构,存在有效的使用情况(在构造函数中强制执行不变式),但要求在其任何部分更改变得笨拙,浪费且更容易引起错误的时候,必须重写整个结构。字段直接。例如,考虑一个PhoneNumber
结构,其字段包括,AreaCode
和Exchange
,并假设其中一个具有List<PhoneNumber>
。以下内容的效果应该很清楚:
为(int i = 0; i <myList.Count; i ++) { PhoneNumber theNumber = myList [i]; 如果(theNumber.AreaCode ==“ 312”) { 字符串newExchange =“”; 如果(new312to708Exchanges.TryGetValue(theNumber.Exchange),出了newExchange) { theNumber.AreaCode =“ 708”; theNumber.Exchange = newExchange; myList [i] = theNumber; } } }
请注意,以上代码中的任何内容都不知道或不在乎and PhoneNumber
以外的任何字段。如果是所谓的“不可变”结构,则有必要为每个字段提供一个方法,该方法将返回一个新的结构实例,该实例在指定的字段中保存传入的值,否则将有必要像上面的代码了解结构中的每个字段。不太吸引人。AreaCode
Exchange
PhoneNumber
withXX
顺便说一句,在至少两种情况下,结构可以合理地持有对可变类型的引用。
在前一种情况下,对象的IDENTITY将是不可变的。在第二种方法中,嵌套对象的类可能不强制不变性,但是保存引用的结构将强制不变。
我倾向于仅在需要一种方法时才使用结构,从而使类看起来“过于繁重”。因此,有时我将结构视为轻量级对象。注意:这并不总是有效,也不总是最好的做法,所以这实际上取决于情况!
额外资源
有关更正式的解释,MSDN表示:http : //msdn.microsoft.com/zh-cn/library/ms229017.aspx 此链接验证了我上面提到的内容,结构仅应用于容纳少量数据。
将结构用于纯数据结构,将类用于具有操作的对象。
如果您的数据结构不需要访问控制并且除了get / set之外没有其他特殊操作,请使用结构。这很明显所有结构都是数据的容器。
如果您的结构具有以任何更复杂的方式修改结构的操作,请使用一个类。一个类还可以通过方法的参数与其他类进行交互。
可以把它想成是砖头和飞机之间的区别。砖是结构。它具有长度,宽度和高度。它做不了什么。一架飞机可能会进行多种改变操作的操作,例如移动控制面和改变发动机推力。作为一个班级会更好。