简体   繁体   English

为什么 C# 结构是不可变的?

[英]Why are C# structs immutable?

I was just curious to know why structs, strings etc are immutable?我只是想知道为什么结构、字符串等是不可变的? What is the reason for making them immutable and rest of the objects as mutable.使它们不可变而其余对象可变的原因是什么。 What are the things that are considered to make an object immutable?哪些东西被认为是使对象不可变的?

Is there any difference on the way how memory is allocated and deallocated for mutable and immutable objects?为可变和不可变对象分配和释放内存的方式有什么不同吗?

If this subject interests you, I have a number of articles about immutable programming at https://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one/如果您对这个主题感兴趣,我在https://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one/ 上有许多关于不可变编程的文章

I was just curious to know why structs, strings etc are immutable?我只是想知道为什么结构、字符串等是不可变的?

Structs and classes are not immutable by default, though it is a best practice to make structs immutable.默认情况下,结构和类不是不可变的,尽管使结构不可变是最佳实践。 I like immutable classes too.我也喜欢不可变的类。

Strings are immutable.字符串是不可变的。

What is the reason for making them immutable and rest of the objects as mutable.使它们不可变而其余对象可变的原因是什么。

Reasons to make all types immutable:使所有类型不可变的原因:

  • It is easier to reason about objects that do not change.对不变的对象进行推理更容易。 If I have a queue with three items in it, I know it is not empty now, it was not empty five minutes ago, it will not be empty in the future.如果我有一个包含三个项目的队列,我知道它现在不是空的,五分钟前不是空的,将来也不会是空的。 It's immutable!它是不可变的! Once I know a fact about it, I can use that fact forever.一旦我知道了一个事实,我就可以永远使用这个事实。 Facts about immutable objects do not go stale.关于不可变对象的事实不会过时。

  • A special case of the first point: immutable objects are much easier to make threadsafe.第一点的特例:不可变对象更容易使线程安全。 Most thread safety problems are due to writes on one thread and reads on another;大多数线程安全问题是由于在一个线程上写入而在另一个线程上读取; immutable objects don't have writes.不可变对象没有写入。

  • Immutable objects can be taken apart and re-used.不可变对象可以被拆开并重新使用。 For example, if you have an immutable binary tree then you can use its left and right subtrees as subtrees of a different tree without worrying about it.例如,如果您有一个不可变的二叉树,那么您可以将其左右子树用作不同树的子树而无需担心。 In a mutable structure you typically end up making copies of data to re-use it because you don't want changes to one logical object affecting another.在可变结构中,您通常最终会制作数据副本以重新使用它,因为您不希望对一个逻辑对象的更改影响另一个。 This can save lots of time and memory.这可以节省大量时间和内存。

Reasons to make structs immutable使结构不可变的原因

There are lots of reasons to make structs immutable.使结构不可变的原因有很多。 Here's just one.这里只有一个。

Structs are copied by value, not by reference.结构是按值复制的,而不是按引用复制的。 It is easy to accidentally treat a struct as being copied by reference.很容易意外地将结构视为通过引用复制。 For example:例如:

void M()
{
    S s = whatever;
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
    Console.WriteLine(s.Foo);
    ...
}

Now you want to refactor some of that code into a helper method:现在您想将其中的一些代码重构为一个辅助方法:

void Helper(S s)
{
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
}

WRONG!错了! That should be (ref S s) -- if you don't do that then the mutation will happen on a copy of s.那应该是 (ref S s)——如果你不这样做,那么突变将发生在 s 的副本上。 If you don't allow mutations in the first place then all these sorts of problems go away.如果您一开始就不允许突变,那么所有这些问题都会消失。

Reasons to make strings immutable使字符串不可变的原因

Remember my first point about facts about immutable structures staying facts?还记得我关于不可变结构保持事实的第一点吗?

Suppose string were mutable:假设字符串是可变的:

public static File OpenFile(string filename)
{
    if (!HasPermission(filename)) throw new SecurityException();
    return InternalOpenFile(filename);
}

What if the hostile caller mutates filename after the security check and before the file is opened?如果安全检查,文件之前敌对呼叫者变异名被打开怎么办? The code just opened a file that they might not have permission to!代码只是打开了一个他们可能没有权限的文件!

Again, mutable data is hard to reason about.同样,可变数据很难推理。 You want the fact "this caller is authorized to see the file described by this string" to be true forever , not until a mutation happens .您希望“此调用者有权查看此字符串描述的文件”这一事实永远成立直到发生突变 With mutable strings, to write secure code we'd constantly have to be making copies of data that we know do not change.对于可变字符串,为了编写安全代码,我们必须不断地制作我们知道不会改变的数据副本。

What are the things that are considered to make an object immutable?哪些东西被认为是使对象不可变的?

Does the type logically represent something that is an "eternal" value?该类型是否在逻辑上代表了“永恒”值? The number 12 is the number 12;数字12就是数字12; it doesn't change.它不会改变。 Integers should be immutable.整数应该是不可变的。 The point (10, 30) is the point (10, 30);点 (10, 30) 是点 (10, 30); it doesn't change.它不会改变。 Points should be immutable.点应该是不可变的。 The string "abc" is the string "abc";字符串“abc”就是字符串“abc”; it doesn't change.它不会改变。 Strings should be immutable.字符串应该是不可变的。 The list (10, 20, 30) doesn't change.列表 (10, 20, 30) 不会改变。 And so on.等等。

Sometimes the type represents things that do change.有时类型代表确实发生变化的事物。 Mary Smith's last name is Smith, but tomorrow she might be Mary Jones.玛丽史密斯的姓是史密斯,但明天她可能会叫玛丽琼斯。 Or Miss Smith today might be Doctor Smith tomorrow.或者今天的史密斯小姐明天可能就是史密斯医生。 The alien has fifty health points now but has ten after being hit by the laser beam.外星人现在有 50 点生命值,但在被激光束击中后有 10 点。 Some things are best represented as mutations.有些东西最好用突变来表示。

Is there any difference on the way how memory is allocated and deallocated for mutable and immutable objects?为可变和不可变对象分配和释放内存的方式有什么不同吗?

Not as such.不是这样。 As I mentioned before though, one of the nice things about immutable values is that something you can re-use parts of them without making copies.不过,正如我之前提到的,不可变值的好处之一是您可以重用它们的一部分而无需复制。 So in that sense, memory allocation can be very different.所以从这个意义上说,内存分配可能会有很大不同。

Structs are not necessarily immutable, but mutable structs are evil.结构不一定是不可变的,但可变结构是邪恶的。

Creating mutable structs can lead to all kinds of strange behavior in your application and, therefore, they are considered a very bad idea (stemming from the fact that they look like a reference type but are actually a value type and will be copied whenever you pass them around).创建可变结构可能会导致应用程序中出现各种奇怪的行为,因此,它们被认为是一个非常糟糕的主意(因为它们看起来像引用类型,但实际上是值类型,并且每当您通过时都会被复制)他们周围)。

Strings, on the other hand, are immutable.另一方面,字符串是不可变的。 This makes them inherently thread-safe as well as allowing for optimizations via string interning.这使得它们本质上是线程安全的,并允许通过字符串实习进行优化。 If you need to construct a complicated string on the fly, you can use StringBuilder .如果您需要即时构造复杂的字符串,可以使用StringBuilder

The concepts of mutability and immutability have different meanings when applied to structs and classes.可变性和不变性的概念在应用于结构和类时具有不同的含义。 A key aspect (oftentimes, the key weakness) of mutable classes is if Foo has a field Bar of type List<Integer> , which holds a reference to a list containing (1,2,3), other code which has a reference to that same list could modify it, such that Bar holds a reference to a list containing (4,5,6), even if that other code has no access whatsoever to Bar .可变类的一个关键方面(通常是关键弱点)是如果Foo有一个List<Integer>类型的字段Bar ,它持有对包含 (1,2,3) 的列表的引用,其他代码引用同一个列表可以修改它,这样Bar持有对包含 (4,5,6) 的列表的引用,即使其他代码无法访问Bar By contrast, if Foo had a field Biz of type System.Drawing.Point , the only way anything could modify any aspect of Biz would be to have write access to that field .相比之下,如果Foo有一个System.Drawing.Point类型的字段Biz ,那么任何可以修改Biz任何方面的唯一方法就是对该字段具有写访问权限

The fields (public and private) of a struct can be mutated by any code which can mutate the storage location in which the struct is stored, and cannot be mutated by any code which cannot mutate the storage location where it is stored.结构体的字段(公共和私有)可以被任何可以改变结构体存储位置的代码改变,并且不能被任何不能改变结构体存储位置的代码改变。 If all of the information encapsulated within a struct is held in its fields, such a struct can effectively combine the control of an immutable type with the convenience of a mutable type, unless the struct is coded in such a way as to remove such convenience (a habit which, unfortunately, some Microsoft programmers recommend).如果结构体中封装的所有信息都保存在其字段中,则这样的结构体可以有效地将不可变类型的控制与可变类型的便利性结合起来,除非该结构体的编码方式消除了这种便利性(不幸的是,一些微软程序员推荐了一个习惯)。

The "problem" with structs is that when a method (including a property implementation) is invoked on a struct in a read-only context (or immutable location), the system copies the struct, performs the method on the temporary copy, and silently discards the result.结构体的“问题”在于,当在只读上下文(或不可变位置)中的结构体上调用方法(包括属性实现)时,系统会复制该结构体,在临时副本上执行该方法,然后静默丢弃结果。 This behavior has led programmers to put forth the unfortunate notion that the way to avoid problems with mutating methods is to have many structs disallow piecewise updates, when the problems could have been better avoided by simply replacing properties with exposed fields .这种行为导致程序员提出了一个不幸的想法,即避免变异方法问题的方法是让许多结构禁止分段更新,而通过简单地用公开的字段替换属性可以更好地避免问题。

Incidentally, some people complain that when a class property returns a conveniently-mutable struct, changes to the struct don't affect the class from which it came.顺便说一句,有些人抱怨当类属性返回一个方便可变的结构时,对结构的更改不会影响它来自的类。 I would posit that's a good thing--the fact that the returned item is a struct makes the behavior clear (especially if it's an exposed-field struct).我认为这是一件好事——返回的项目是一个结构的事实使行为变得清晰(特别是如果它是一个暴露的字段结构)。 Compare a snippet using a hypothetical struct and property on Drawing.Matrix with one using an actual property on that class as implemented by Microsoft:将使用Drawing.Matrix上的假设结构和属性的片段与使用 Microsoft 实现的该类上的实际属性的片段进行比较:

// Hypothetical struct
public struct {
  public float xx,xy,yx,yy,dx,dy;
} Transform2d;

// Hypothetical property of "System.Drawing.Drawing2d.Matrix"
public Transform2d Transform {get;}

// Actual property of "System.Drawing.Drawing2d.Matrix"
public float[] Elements { get; }

// Code using hypothetical struct
Transform2d myTransform = myMatrix.Transform;
myTransform.dx += 20;
... other code using myTransform

// Code using actual Microsoft property
float[] myArray = myMatrix.Elements;
myArray[4] += 20;
... other code using myArray

Looking at the actual Microsoft property, is there any way to tell whether the write to myArray[4] will affect myMatrix ?查看实际的 Microsoft 属性,有什么方法可以判断写入myArray[4]是否会影响myMatrix Even looking at the page http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx is there any way to tell?甚至看页面http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx有什么办法告诉吗? If the property had been written using the struct-based equivalent, there would be no confusion;如果属性是使用基于结构的等价物编写的,就不会有混淆; the property that returns the struct would return nothing more nor less than the present value of six numbers.返回结构的属性不会返回比六个数字的当前值更多或更少的值。 Changing myTransform.dx would be nothing more nor less than a write to a floating-point variable which was unattached to anything else.更改myTransform.dx只不过是写入一个不附加任何其他内容的浮点变量。 Anyone who doesn't like the fact that changing myTransform.dx doesn't affect myMatrix should be equally annoyed that writing myArray[4] doesn't affect myMatrix either, except that the independence of myMatrix and myTransform is apparent, while the independence of myMatrix and myArray is not.任何不喜欢改变myTransform.dx不影响myMatrix应该同样恼火,写myArray[4]也不影响myMatrix ,除了myMatrixmyTransform的独立性很明显,而myMatrixmyArray不是。

A struct type is not immutable.结构类型不是一成不变的。 Yes, strings are.是的,字符串是。 Making your own type immutable is easy, simply don't provide a default constructor, make all fields private and define no methods or properties that change a field value.使您自己的类型不可变很容易,只需不提供默认构造函数,将所有字段设为私有并且不定义更改字段值的方法或属性。 Have a method that should mutate the object return a new object instead.有一个应该改变对象的方法返回一个新对象。 There is a memory management angle, you tend to create a lot of copies and garbage.有一个内存管理角度,你往往会创建大量的副本和垃圾。

Structs can be mutable, but it's a bad idea because they have copy-semantics.结构可以是可变的,但这是一个坏主意,因为它们具有复制语义。 If you make a change to a struct, you might actually be modifying a copy.如果对结构进行更改,则实际上可能是在修改副本。 Keeping track of exactly what has been changed is very tricky.准确跟踪已更改的内容非常棘手。

Mutable structs breed mistakes.可变结构会导致错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM