在这里的SO讨论之后,我已经多次看到这样的评论,即可变结构体是“邪恶的”(就像在这个问题的答案中)。

c#中的可变性和结构的实际问题是什么?


当前回答

具有公共可变字段或属性的结构并不邪恶。

Struct methods (as distinct from property setters) which mutate "this" are somewhat evil, only because .net doesn't provide a means of distinguishing them from methods which do not. Struct methods that do not mutate "this" should be invokable even on read-only structs without any need for defensive copying. Methods which do mutate "this" should not be invokable at all on read-only structs. Since .net doesn't want to forbid struct methods that don't modify "this" from being invoked on read-only structs, but doesn't want to allow read-only structs to be mutated, it defensively copies structs in read-only contexts, arguably getting the worst of both worlds.

尽管在只读上下文中处理自突变方法存在问题,但是,可变结构通常提供的语义要比可变类类型优越得多。考虑以下三个方法签名:

struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};

void Method1(PointyStruct foo);
void Method2(ref PointyStruct foo);
void Method3(PointyClass foo);

对于每种方法,请回答以下问题:

假设该方法没有使用任何“不安全”代码,它会修改foo吗? 如果在调用方法之前没有对'foo'的外部引用,那么在调用方法之后是否可以存在外部引用?

答案:

问题1: Method1(): no(意图明确) Method2(): yes(明确的意图) Method3(): yes(不确定意图) 问题2: Method1():没有 Method2(): no(除非不安全) Method3():是的

Method1 can't modify foo, and never gets a reference. Method2 gets a short-lived reference to foo, which it can use modify the fields of foo any number of times, in any order, until it returns, but it can't persist that reference. Before Method2 returns, unless it uses unsafe code, any and all copies that might have been made of its 'foo' reference will have disappeared. Method3, unlike Method2, gets a promiscuously-sharable reference to foo, and there's no telling what it might do with it. It might not change foo at all, it might change foo and then return, or it might give a reference to foo to another thread which might mutate it in some arbitrary way at some arbitrary future time. The only way to limit what Method3 might do to a mutable class object passed into it would be to encapsulate the mutable object into a read-only wrapper, which is ugly and cumbersome.

结构数组提供了美妙的语义。给定矩形类型的RectArray[500],如何将元素123复制到元素456,然后在不影响元素456的情况下,将元素123的宽度设置为555是显而易见的。"RectArray[432] = RectArray[321];…;RectArray[123]。宽度= 555;"。知道Rectangle是一个具有名为Width的整数字段的结构体,就可以知道关于上述语句的所有信息。

Now suppose RectClass was a class with the same fields as Rectangle and one wanted to do the same operations on a RectClassArray[500] of type RectClass. Perhaps the array is supposed to hold 500 pre-initialized immutable references to mutable RectClass objects. in that case, the proper code would be something like "RectClassArray[321].SetBounds(RectClassArray[456]); ...; RectClassArray[321].X = 555;". Perhaps the array is assumed to hold instances that aren't going to change, so the proper code would be more like "RectClassArray[321] = RectClassArray[456]; ...; RectClassArray[321] = New RectClass(RectClassArray[321]); RectClassArray[321].X = 555;" To know what one is supposed to do, one would have to know a lot more both about RectClass (e.g. does it support a copy constructor, a copy-from method, etc.) and the intended usage of the array. Nowhere near as clean as using a struct.

To be sure, there is unfortunately no nice way for any container class other than an array to offer the clean semantics of a struct array. The best one could do, if one wanted a collection to be indexed with e.g. a string, would probably be to offer a generic "ActOnItem" method which would accept a string for the index, a generic parameter, and a delegate which would be passed by reference both the generic parameter and the collection item. That would allow nearly the same semantics as struct arrays, but unless the vb.net and C# people can be pursuaded to offer a nice syntax, the code is going to be clunky-looking even if it is reasonably performance (passing a generic parameter would allow for use of a static delegate and would avoid any need to create any temporary class instances).

就我个人而言,我对Eric Lippert等人对可变值类型的憎恨感到恼火。它们提供了比到处使用的混杂引用类型清晰得多的语义。尽管.net对值类型的支持有一些限制,但在许多情况下,可变值类型比任何其他类型的实体都更适合。

其他回答

Value types basically represents immutable concepts. Fx, it makes no sense to have a mathematical value such as an integer, vector etc. and then be able to modify it. That would be like redefining the meaning of a value. Instead of changing a value type, it makes more sense to assign another unique value. Think about the fact that value types are compared by comparing all the values of its properties. The point is that if the properties are the same then it is the same universal representation of that value.

正如Konrad所提到的,更改日期也没有意义,因为值代表的是唯一的时间点,而不是具有任何状态或上下文依赖关系的时间对象的实例。

希望这能让你明白。可以肯定的是,它更多的是关于您试图用值类型捕获的概念,而不是实际的细节。

可变数据有许多优点和缺点。最大的缺点就是别名。如果相同的值在多个地方使用,其中一个地方更改了它,那么它将神奇地更改到正在使用它的其他地方。这与竞态条件有关,但并不完全相同。

有时候,价值百万美元的优势是模块化。可变状态允许您向代码隐藏更改的信息,而代码不需要知道这些信息。

《解释器的艺术》详细讨论了这些权衡,并给出了一些例子。

具有公共可变字段或属性的结构并不邪恶。

Struct methods (as distinct from property setters) which mutate "this" are somewhat evil, only because .net doesn't provide a means of distinguishing them from methods which do not. Struct methods that do not mutate "this" should be invokable even on read-only structs without any need for defensive copying. Methods which do mutate "this" should not be invokable at all on read-only structs. Since .net doesn't want to forbid struct methods that don't modify "this" from being invoked on read-only structs, but doesn't want to allow read-only structs to be mutated, it defensively copies structs in read-only contexts, arguably getting the worst of both worlds.

尽管在只读上下文中处理自突变方法存在问题,但是,可变结构通常提供的语义要比可变类类型优越得多。考虑以下三个方法签名:

struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};

void Method1(PointyStruct foo);
void Method2(ref PointyStruct foo);
void Method3(PointyClass foo);

对于每种方法,请回答以下问题:

假设该方法没有使用任何“不安全”代码,它会修改foo吗? 如果在调用方法之前没有对'foo'的外部引用,那么在调用方法之后是否可以存在外部引用?

答案:

问题1: Method1(): no(意图明确) Method2(): yes(明确的意图) Method3(): yes(不确定意图) 问题2: Method1():没有 Method2(): no(除非不安全) Method3():是的

Method1 can't modify foo, and never gets a reference. Method2 gets a short-lived reference to foo, which it can use modify the fields of foo any number of times, in any order, until it returns, but it can't persist that reference. Before Method2 returns, unless it uses unsafe code, any and all copies that might have been made of its 'foo' reference will have disappeared. Method3, unlike Method2, gets a promiscuously-sharable reference to foo, and there's no telling what it might do with it. It might not change foo at all, it might change foo and then return, or it might give a reference to foo to another thread which might mutate it in some arbitrary way at some arbitrary future time. The only way to limit what Method3 might do to a mutable class object passed into it would be to encapsulate the mutable object into a read-only wrapper, which is ugly and cumbersome.

结构数组提供了美妙的语义。给定矩形类型的RectArray[500],如何将元素123复制到元素456,然后在不影响元素456的情况下,将元素123的宽度设置为555是显而易见的。"RectArray[432] = RectArray[321];…;RectArray[123]。宽度= 555;"。知道Rectangle是一个具有名为Width的整数字段的结构体,就可以知道关于上述语句的所有信息。

Now suppose RectClass was a class with the same fields as Rectangle and one wanted to do the same operations on a RectClassArray[500] of type RectClass. Perhaps the array is supposed to hold 500 pre-initialized immutable references to mutable RectClass objects. in that case, the proper code would be something like "RectClassArray[321].SetBounds(RectClassArray[456]); ...; RectClassArray[321].X = 555;". Perhaps the array is assumed to hold instances that aren't going to change, so the proper code would be more like "RectClassArray[321] = RectClassArray[456]; ...; RectClassArray[321] = New RectClass(RectClassArray[321]); RectClassArray[321].X = 555;" To know what one is supposed to do, one would have to know a lot more both about RectClass (e.g. does it support a copy constructor, a copy-from method, etc.) and the intended usage of the array. Nowhere near as clean as using a struct.

To be sure, there is unfortunately no nice way for any container class other than an array to offer the clean semantics of a struct array. The best one could do, if one wanted a collection to be indexed with e.g. a string, would probably be to offer a generic "ActOnItem" method which would accept a string for the index, a generic parameter, and a delegate which would be passed by reference both the generic parameter and the collection item. That would allow nearly the same semantics as struct arrays, but unless the vb.net and C# people can be pursuaded to offer a nice syntax, the code is going to be clunky-looking even if it is reasonably performance (passing a generic parameter would allow for use of a static delegate and would avoid any need to create any temporary class instances).

就我个人而言,我对Eric Lippert等人对可变值类型的憎恨感到恼火。它们提供了比到处使用的混杂引用类型清晰得多的语义。尽管.net对值类型的支持有一些限制,但在许多情况下,可变值类型比任何其他类型的实体都更适合。

它与结构无关(也与c#无关),但在Java中,当可变对象是哈希映射中的键时,你可能会遇到问题。如果你在将它们添加到映射后更改它们,它也更改了哈希代码,可能会发生糟糕的事情。

可变结构体并不邪恶。

在高绩效环境下,它们是绝对必要的。例如,当缓存线和垃圾收集成为瓶颈时。

我不认为在这些完全有效的用例中使用不可变结构体是“邪恶的”。

我可以看到c#的语法没有帮助区分值类型或引用类型的成员的访问,所以我完全赞成使用强制不变性的不可变结构,而不是可变结构。

然而,与其简单地给不可变结构贴上“邪恶”的标签,我建议接受这种语言,提倡更有帮助和建设性的经验法则。

例如:"struct是默认复制的值类型。如果你不想复制他们,你需要一份推荐信。 “首先尝试使用只读结构体”。