C# 中的 immutable 和 readonly 是什么意思?

16

一个不可变对象的值是不可更改的,这个说法正确吗?

我有两个涉及readonly的情况想要了解:

  1. 如果我有一个集合并标记为readonly,像下面这样。我是否仍然可以调用_items.Add

    private readonly ICollection<MyItem> _items;
    
    而且对于下面的变量,如果以后我调用_metadata.Change,它将更改Metadata实例中的一些成员变量的内部值。那么_metadata是否仍然是不可变的?
    private readonly Metadata _metadata;
    
    针对上述两个变量,我完全理解在初始化程序和构造函数之外无法直接给它们赋新值。

1
我确信这个问题以前已经在这里被问过了,但我找不到重复的。有一个关于Java的,但是我找不到关于C#的。 - Anders Abel
8个回答

25

我建议你阅读Eric Lippert的一系列博客文章。第一部分是C#中的不变性第一部分:不变性的种类。这个系列文章非常详细和有帮助,像往常一样。该系列描述了变量被标记为readonlyimmutable等的含义。

通常情况下,readonly只表示在构造函数外部无法重新分配一个字段。只要它保持相同的实例,字段本身可以被修改。因此,你可以向存储在readonly字段中的集合添加元素。

关于可变性,这更加复杂,也取决于你考虑哪种可变性。当Metadata的内部值是引用类型时,并且这些引用本身(指向的实例)不会改变时,你可以说Metadata不发生变化。但是从逻辑上讲,它是被修改的。欲了解更多信息,请参见Eric的文章。


6
好的链接:我只希望“你的回答”的要点不仅在链接里,特别是如果那个链接不再可用的情况下。你需要将那个链接中的要点作为你精心措辞的答案的一部分包含进来,以此赢得+5票的支持。 - IAbstract
@IAbstract:好的,我已经更新了我的答案,使其更加完整。无论如何,其他答案中没有更多的内容,所以我也会点赞它们。 - NOtherDev

7
将一个字段标记为只读只意味着您不能更改该字段的值。它对对象的内部状态没有影响。在您的示例中,虽然您无法将新的元数据对象分配给_metadata字段,也无法将新的ICollection分配给_items字段(除非在构造函数中),但您可以更改存储在这些字段中的现有对象的内部值。

7
一个不可变对象是指一旦创建就无法更改的对象。在C#中,字符串是不可变的。如果您查看字符串操作例程,您会发现它们都会返回一个新的、修改后的字符串,并保留原始字符串不变。
这大大简化了字符串处理。当您引用一个字符串时,您可以确信没有人会在您脚下意外地更改它。
"readonly"是另一回事。它意味着一旦设置,引用就不能更改,并且只能在对象构造期间设置。在您的示例中,您可以更改"_items"的内容或"_metadata"的属性,但不能将另一个"ICollection"分配给"_items"成员,也不能将另一个"Metadata"实例分配给"_metadata"。
"readonly"是在对象引用上设置的。不可变性是对象本身的属性。它们可以自由组合。为了确保一个属性不被任何方式改变,它应该是一个对不可变对象的只读引用。

4

在存储在readonly字段中的对象上进行突变没有任何阻止。因此,您可以在构造函数/初始化程序之外调用_items.Add()metadata._Change()。只有在构建后才会使用readonly来阻止对这些字段分配新值。


3

private readonly ICollection<MyItem> _items;

这段代码并不能防止向_items中添加元素。它只是防止重新分配_items的引用。同样,对于_metadata也是如此。可以更改_metadata中可访问成员的值,但不能重新分配_metadata的引用。


2
  1. 是的。
  2. 只读并不等于不可变。您仍然可以调用_metadata.Change。

2

readonly关键字适用于变量 - 这意味着您不能将另一个值赋给它,但可以更改其内部状态。这就是为什么您可以更改集合中的项目,但不能将新集合分配给变量的原因。


1
这很令人困惑。readonly 应用于成员 变量 -- 完全与引用无关。考虑:readonly int x = 2; - user166390

0
在像C++这样的语言中,关键字“const”有相当多不同的用途,对于开发人员来说,传递常量参数和常量指针的值部分是容易的。
但在C#中却不太容易。我们需要通过定义构建不可变类,这意味着一旦创建了一个实例,就没有办法通过编程方式进行更改。
Java由于关键字final及其用法的存在,比C#稍微容易一些。
让我们考虑这个例子并看看它是如何不可变的。
public sealed class ImmutableFoo
    {
        private ImmutableFoo()
        {

        }

        public string SomeValue { get; private set; }
        //more properties go here

        public sealed class Builder
        {
            private readonly ImmutableFoo _instanceFoo = new ImmutableFoo();
            public Builder SetSomeValue(string someValue)
            {
                _instanceFoo.SomeValue = someValue;
                return this;
            }

            /// Set more properties go here
            /// 

            public ImmutableFoo Build()
            {
                return _instanceFoo;
            }
        }
    }

你可以像这样使用它

public class Program
    {
        public static void Main(string[] args)
        {
            ImmutableFoo foo = new ImmutableFoo.Builder()
                .SetSomeValue("Assil is my name")
                .Build();

            Console.WriteLine(foo.SomeValue);
            Console.WriteLine("Hit enter to terminate");
            Console.ReadLine();
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接