什么时候使用结构而不是类?[关闭]


174

您何时使用结构与类的经验法则是什么?我正在考虑这些术语的C#定义,但是如果您的语言具有类似的概念,我也想听听您的意见。

我倾向于对几乎所有东西使用类,并且仅在某些东西非常简单并且应该是值类型(例如PhoneNumber或类似的东西)时才使用structs。但这似乎是一个相对较小的用途,我希望有更多有趣的用例。



7
@Frustrated不能将问题标记为另一个站点上某事物的重复,尽管它肯定是相关的。
亚当李尔

@Anna Lear:我知道,我投票决定不迁移为重复副本。迁移后,可以将其作为副本正确关闭。
FrustratedWithFormsDesigner

5
@沮丧我认为不需要迁移它。
亚当李尔

15
这似乎是一个更适合P.SE与SO的问题。这就是为什么我在这里问它。
RationalGeek

Answers:


169

遵循的一般规则是,结构应该是相关属性的小型,简单(一级)集合,一旦创建就不可变。对于其他任何事情,请使用一个类。

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的其他数量,其中操作必须考虑值的所有部分),然后使用结构并进行相应的设计。在大多数其他情况下,对象的子项应独立可变,请使用类。


1
好答案!我指的是将其阅读为“域驱动开发”方法论和书籍,这似乎与您的观点有关,即为什么应该根据您实际尝试实现的概念使用类或结构
Maxim Popravko 2011年

1
值得一提的只是一点细节:您不能在结构上定义自己的默认构造函数。您必须将所有内容初始化为其默认值。通常,这是次要的,特别是在适合作为结构体的类上。但这意味着将类更改为结构可能意味着不仅仅是更改关键字。
Laurent Bourgault-Roy 2012年

1
@ LaurentBourgault-Roy-是的。还有其他陷阱。例如,结构不能具有继承层次结构。它们可以实现接口,但不能派生自System.ValueType以外的任何对象(暗含它们的结构)。
KeithS 2012年

作为一个JavaScript开发人员,我必须问两件事。对象文字与结构的典型用法有何不同?为什么结构不可变很重要?
埃里克·雷彭

1
@ErikReppen:回答您的两个问题:将结构传递给函数时,它按值传递。对于类,您正在传递对该类的引用(仍然按值)。埃里克·利珀特(Eric Lippert)讨论了这个问题的原因:blogs.msdn.com/b/ericlippert/archive/2008/05/14/…。KeithS的最后一段也涵盖了这些问题。
Brian

14

答案:“对于纯数据结构使用结构,对带有操作的对象使用类”绝对是错误的IMO。如果一个结构拥有大量的属性,那么一个类几乎总是更合适。从效率的角度来看,Microsoft经常说,如果您的类型大于16字节,则应该是一个类。

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes


1
绝对。如果可以的话,我会投票赞成。我不知道这个想法从何而来:结构是针对数据而对象不是。C#中的结构只是在堆栈上分配的一种机制。传递整个结构的副本,而不仅仅是对象指针的副本。
mike30

2
@mike-C#结构不一定在堆栈上分配。

1
@mike“结构用于数据”的想法可能来自多年的C编程习惯。C没有类,其结构是仅数据容器。
Konamiman

显然,至少有3位C程序员(被赞成投票)已经阅读了Michael K的答案;-)
bytedev

10

a class和a 之间最重要的区别struct是在以下情况下会发生什么:

  事物thing1 = new Thing();
  something1.somePropertyOrField = 5;
  事物thing2 =事物1;
  something2.somePropertyOrField = 9;

最后一条语句的作用是thing1.somePropertyOrField什么?如果Thing一个struct且somePropertyOrField是一个公开的公共字段,则对象thing1thing2将彼此“分离”,因此后一种语句将不受影响thing1。如果Thing是类,则thing1thing2将彼此连接,因此后一个语句将写入thing1.somePropertyOrField。在前一种语义更有意义的情况下,应该使用一个结构,而在后一种语义更有意义的情况下,应该使用一个类。

请注意,尽管有些人建议使某事物变得可变的愿望是支持它成为类的一个论据,但我建议反之亦然:如果为了保存某些数据而存在的某物将是可变的,并且如果不清楚是否将实例附加到任何事物,则该事物应该是一个结构(可能具有暴露的字段),以明确实例未附加到任何其他事物。

例如,考虑以下语句:

  人somePerson = myPeople.GetPerson(“ 123-45-6789”);
  somePerson.Name =“玛丽·约翰逊”; //曾经是“玛丽·史密斯”

第二条语句会更改存储在其中的信息myPeople吗?如果Person是一个暴露场结构,它就不会,而且事实并非如此,这显然是因为它是一个暴露场结构;如果Person是一个结构并且希望更新myPeople,那么显然必须做一些事情myPeople.UpdatePerson("123-45-6789", somePerson)Person但是,如果是类,则可能很难确定以上代码是从不更新的内容MyPeople,始终更新它,还是有时对其进行更新。

关于结构应该是“不变的”的观点,我总体上不同意。对于“不可变的”结构,存在有效的使用情况(在构造函数中强制执行不变式),但要求在其任何部分更改变得笨拙,浪费且更容易引起错误的时候,必须重写整个结构。字段直接。例如,考虑一个PhoneNumber结构,其字段包括,AreaCodeExchange,并假设其中一个具有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以外的任何字段。如果是所谓的“不可变”结构,则有必要为每个字段提供一个方法,该方法将返回一个新的结构实例,该实例在指定的字段中保存传入的值,否则将有必要像上面的代码了解结构中的每个字段。不太吸引人。AreaCodeExchangePhoneNumberwithXX

顺便说一句,在至少两种情况下,结构可以合理地持有对可变类型的引用。

  1. 结构的语义表明,该结构存储的是有关对象的标识,而不是作为保存对象属性的快捷方式。例如,“ KeyValuePair”将保存某些按钮的标识,但不会保留有关这些按钮的位置,突出显示状态等的持久性信息。
  2. 该结构知道它拥有对该对象的唯一引用,没有其他人将获得该引用,并且对该对象执行的任何修改都将在对该对象的引用存储到任何地方之前进行。

在前一种情况下,对象的IDENTITY将是不可变的。在第二种方法中,嵌套对象的类可能不强制不变性,但是保存引用的结构将强制不变。


7

我倾向于仅在需要一种方法时才使用结构,从而使类看起来“过于繁重”。因此,有时我将结构视为轻量级对象。注意:这并不总是有效,也不总是最好的做法,所以这实际上取决于情况!

额外资源

有关更正式的解释,MSDN表示:http : //msdn.microsoft.com/zh-cn/library/ms229017.aspx 此链接验证了我上面提到的内容,结构仅应用于容纳少量数据。


5
在另一个站点上的问题(即使该站点是Stack Overflow)也不视为重复项。请扩展您的答案以包括实际答案,而不仅仅是指向其他地方的链接。如果链接改变了,那么您现在写的答案将毫无用处,我们希望保留信息并使程序员尽可能独立。谢谢!
亚当李尔

2
@Anna Lear感谢您让我知道,从现在开始请记住这一点!
2011年

2
-1“我倾向于仅在需要一种方法时才使用结构,因此使类看起来太“沉重”。” ...重吗?那个的真实意义是什么?...而您是否真的在说,因为cos只有一种方法,那么它可能应该是一种结构?
bytedev 2013年

2

将结构用于纯数据结构,将类用于具有操作的对象。

如果您的数据结构不需要访问控制并且除了get / set之外没有其他特殊操作,请使用结构。这很明显所有结构都是数据的容器。

如果您的结构具有以任何更复杂的方式修改结构的操作,请使用一个类。一个类还可以通过方法的参数与其他类进行交互。

可以把它想成是砖头和飞机之间的区别。砖是结构。它具有长度,宽度和高度。它做不了什么。一架飞机可能会进行多种改变操作的操作,例如移动控制面和改变发动机推力。作为一个班级会更好。


2
在C#中,“对纯数据结构使用结构,对带有操作的对象使用类”是胡说八道。请参阅下面的答案。
bytedev 2014年

1
“将结构用于纯数据结构,将类用于具有操作的对象。” 对于C / C ++,而不是C#,这是正确的
taktak004
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.