C#中的不可变类型和属性

32

什么是C#中的不可变类型和不可变属性?你能给出一个简单的例子吗?

4个回答

35

不可变类型是指在初始化时其属性值可以被设置,一旦对象创建后,就不能再做任何更改。而不可变属性则是只读属性。

在下面的示例中,ImmutableType 是一个不可变类型,它有一个名为Test的属性。这是一个只读属性,只能在构造函数中进行设置。

class ImmutableType
{
    private readonly string _test;
    public string Test
    {
        get { return _test; }
    }

    public ImmutableType(string test)
    {
        _test = test;
    }
}

参见维基百科文章,以及有关该主题的一些Stack Overflow问题回答


4
你可以在变量名_test前加上readonly来防止未来类的更改导致它可变。遗憾的是,C#中没有不可变的自动实现属性。 - xofz
3
当属性是引用类型时,这就变得有些棘手。因此,不可变的定义可以被解释为“引用”是不可变的,或者“对象图”是不可变的(具体取决于您的需求,但通常是后者 - 参见维基文章)。 - Ryan Emerle
1
使用引用类型,意味着你只能实例化并分配一次。例如,我可以有一个“只读”列表,但我可以随意向其中添加和删除元素。 - Jaxidian
3
注:Sam的评论不再正确。从C# 6开始,您可以只使用 { get; } 来拥有不可变的自动实现属性。 - Scott Chamberlain
可以在字符串“Test”前面去掉public吗?我想是的。 - stephanmg
C#9有一些扩展方法来使事物不可变:init-only-setters record-types - mishal153

9
除了 @fretje 上面的回答之外,在 C#6 及更高版本中,现在已经实现了 getter-only auto properties,这允许创建不可变的自动属性而无需额外的显式 private readonly 后备字段。等效的代码将被缩写为:
class ImmutableType
{
    public string Test
    {
        get; // No Set at all, not even a private set.
    }

    public ImmutableType(string test)
    {
        Test = test; // The compiler understands this and initializes the backing field
    }
}

请注意,private set 仅提供了对同一类中属性更改的受限封装,因此并不是真正的不可变性:
public string Test
{
    get;
    private set;  // Not immutable, since this is still mutable from within the class
}

更多关于不可变性

正如其他人所说,不可变属性是指一旦设置就无法更改的属性。在构造期间设置“唯一”值。

不可变类型是指所有(外部可见)属性和字段都是不可变的类型 - 例如最初计划用于 C#7 的“Record”类型(希望现在是8) 将是一个不可变类型。其他不可变类型的示例包括 Tuples 和所有 匿名类

C#中的不可变字段应使用 readonly 关键字进行限定 - 这由编译器强制执行,以确保没有其他代码尝试在构造函数之外更改该字段。

尽可能使用不变的字段、变量和属性被视为良好的实践,因为这将大大减少错误的表面积(由于字段代表对象的状态,防止对字段的更改会减少状态的数量)。
不可变性的好处在多线程程序中尤为重要,其中两个或多个线程同时访问同一对象。由于多个并发读取线程可以安全地读取字段或属性的值,因此程序员不需要担心与其他线程对字段的更改相关的线程安全问题(因为禁止对属性进行更改)。
当处理由多个组成对象组成的复杂对象时,不可变性的一个常见缺点是整个图必须“一次性”构建,这可能会导致混乱的代码。这里的常见解决方案是使用Builder pattern作为脚手架,它允许逐步构建瞬态、可变表示,然后在最终的.Build()步骤中获得最终的不可变对象。

3

fretje是正确的。不可变类型的最流行的例子是C#中的string对象。这就是StringBuilder存在的全部原因。


-1

C#中对于不可变性(immutability)没有明确的定义:

  • 通常情况下,它要求所有公共字段为只读(readonly),并且所有公共属性具有init setter或无setter。

  • 它也可以将此扩展到私有成员(尽管通常使用可变的私有成员来缓存值,例如哈希码)

  • 它还可能意味着所有成员本身都是不可变类型。否则,消费者仍然可以通过调用成员方法来修改对象

  • 它还可能意味着所有方法在使用相同参数调用时始终返回相同的输出。特别是一个不可变的GetHashCode()意味着该对象可以安全地用作字典等中的键

更强大且通常更有用的概念是“数据”(Data)——一种不可变类型,也具有值语义(通常通过从IEquatable<T>继承实现)

以下是一个满足上述所有要求的“数据”类型示例:

record MyData(int age, DateTime JoinDate);

(请参见项目F,了解有关数据类型的更多信息)


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