【问题标题】:In C# , Are Value types mutable or immutable ?在 C# 中,值类型是可变的还是不可变的?
【发布时间】:2011-08-17 04:50:38
【问题描述】:

值类型行为表明我们持有的任何值都不能通过其他变量来改变。

但是我仍然对我在这篇文章的标题中提到的内容感到困惑。谁能澄清一下?

【问题讨论】:

    标签: c# value-type reference-type


    【解决方案1】:

    值类型可以是可变的(模一些奇怪的边缘情况)不可变的,这取决于你如何编写它们。

    可变的:

    public struct MutableValueType
    {
        public int MyInt { get; set; }
    }
    

    不可变:

    public struct ImmutableValueType
    {
        private readonly int myInt;
        public ImmutableValueType(int i) { this.myInt = i; }
    
        public int MyInt { get { return this.myInt; } }
    }
    

    内置值类型(intdouble 等)是不可变的,但您可以非常轻松地创建自己的可变 structs。

    一条建议:不要。可变值类型是个坏主意,应该避免。例如,这段代码做了什么:

    SomeType t = new SomeType();
    t.X = 5;
    
    SomeType u = t;
    t.X = 10;
    
    Console.WriteLine(u.X);
    

    视情况而定。如果SomeType 是一个值类型,它会打印5,这是一个相当混乱的结果。

    请参阅this question,了解有关为什么应避免使用可变值类型的更多信息。

    【讨论】:

    • 只有两个观察结果 - 在 mutable 的情况下,该字段可能(并且可能应该)仍然位于属性后面。在 不可变 的情况下,该字段可以显式为 readonly
    • @Marc 好点。我实际上考虑过返回并进行readonly 编辑;假设我现在两个都做。谢谢。
    • @dlev,所以当你有类似下面的东西时。诠释 i = 5;我 = 5+2; 5+2 被传递给一个新的 int 并且被引用的返回给 i?恕我直言,当 SomeType 打印 5 我并不感到困惑,正是 SomeType 打印 10 让我感到困惑。顺便说一句,答案很好。
    • @dlev,但是通过更改 i 持有的内存,我们不会更改 i 的值,使其可变。我一直都知道 Strings 是不可变的,因此当您执行类似 str = str + "change"; 这样的操作时,您实际上是在调用 String。 Concat 创建一个新的内存块,然后将 str 指向该内存块。这是 i 发生的情况还是整数原始内存块发生了变化?希望这是有道理的。
    • 在您的示例中,如果 SomeType 是具有整数字段 X 的值类型,则 u.X 将为 5。该信息和上面的代码足以显​​示该信息;它几乎不应该是“意外的”。现在假设 t 是一个类类型,并且在创建 t 和 WriteLine 调用之间的任何地方——甚至在“t.x=10”之前,就有对 SomeInheritableClassObject.SomeVirtualFunction(t) 的调用。如果不检查 SomeVirtualFunction 的所有可能覆盖,就无法知道 WriteLine 会输出什么。我认为可变结构的可预测行为要好得多。
    【解决方案2】:

    所有原始值类型,如 int、double、float 都是不可变的。但结构本身是可变的。因此您必须采取措施使它们成为不可变的,因为这会造成很多混乱。

    【讨论】:

    • 好的,但是关于引用类型呢?在阅读文档时,我开始知道它们可以是可变的或不可变的。怎么样?
    • @Nitesh 。这也取决于您如何实现它。 System.Text.StringBuilder 类是可变引用类型的一个示例。它包含可以更改类实例值的成员。不可变引用类型的一个示例是 System.String 类。实例化后,它的值永远不会改变
    【解决方案3】:

    保存任何信息的任何值类型实例都可以通过可以写入包含它的存储位置的代码来改变,并且任何值类型实例都不能被不能写入包含它的存储位置的代码改变。这些特性使得可变值类型的私有存储位置在许多情况下成为理想的数据容器,因为它们结合了来自可变性的更新便利性和来自不变性的控制。请注意,可以为值类型编写代码,这样就不可能在没有首先包含所需数据的实例(可能是新创建的临时实例)的情况下改变现有实例,并覆盖前一个实例具有后者的内容,但这不会使值类型或多或少可变,而不是缺少这种能力。在许多情况下,它只会使突变变得尴尬,并使它看起来像这样的陈述:

    我的键值对 = 新 KeyValuePair(MyKeyValuePair.Key+1, MyKeyValuePair.Value+1>;

    将创建一个新实例,但不影响现有实例。如果KeyValuePair 是一个不可变类,并且一个线程正在执行MyKeyValuePair.ToString() 而另一个线程正在执行上述代码,则ToString 调用将作用于旧实例或新实例,因此会产生两个旧值或两个新值。因为KeyValuePair是一个结构体,但是上面的语句会创建一个新的实例,但是它不会让MyKeyValuePair引用这个新的实例——它只会使用这个新的实例作为模板,它的字段将被复制到MyKeyValuePair。如果KeyValuePair 是一个可变结构,则上述代码可能预期含义的最自然表达将更像是:

    MyKeyValuePair.Key += 1; MyKeyValuePair.Value += 1;

    或许:

    var temp = MyKeyValuePair; MyKeyValuePair.Key = temp.Key+1; MyKeyValuePair.Value = temp.Value+1;

    线程的含义会更加清晰。

    【讨论】:

      猜你喜欢
      • 2010-11-20
      • 1970-01-01
      • 2014-08-05
      • 2013-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-01
      相关资源
      最近更新 更多