什么是C#的"大部分完整"(im)可变性方法?

Kit*_*Kit 30 .net c# mutable immutability

由于尽管在CLR中有一些支持,但不变性并没有完全融入C#到F#的程度,或完全进入框架(BCL),对于C#的(im)可变性有什么相当完整的解决方案?

我的偏好顺序是一个由兼容的一般模式/原则组成的解决方案

  • 一个只有很少依赖的开源库
  • 少量互补/兼容的开源库
  • 商业广告

  • 涵盖Lippert的各种不变性
  • 提供了不错的表现(我知道这很模糊)
  • 支持序列化
  • 支持克隆/复制(深/浅/部分?)
  • 在DDD,构建器模式,配置和线程等场景中感觉很自然
  • 提供不可变的集合

我还希望包含您作为社区可能提出的模式,这些模式不完全适合框架,例如通过接口表达可变性意图(其中两个客户端不应该更改某些内容并且可能只想更改某些内容通过接口这样做,而不是支持类(是的,我知道这不是真正的不变性,但足够):

public interface IX
{
    int Y{ get; }
    ReadOnlyCollection<string> Z { get; }
    IMutableX Clone();
}

public interface IMutableX: IX
{
    new int Y{ get; set; }
    new ICollection<string> Z{ get; } // or IList<string>
}

// generally no one should get ahold of an X directly
internal class X: IMutableX
{
    public int Y{ get; set; }

    ICollection<string> IMutableX.Z { get { return z; } }

    public ReadOnlyCollection<string> Z
    {
        get { return new ReadOnlyCollection<string>(z); }
    }

    public IMutableX Clone()
    {
        var c = MemberwiseClone();
        c.z = new List<string>(z);
        return c;
    }

    private IList<string> z = new List<string>();       
}

// ...

public void ContriveExample(IX x)
{
    if (x.Y != 3 || x.Z.Count < 10) return;
    var c= x.Clone();
    c.Y++;
    c.Z.Clear();
    c.Z.Add("Bye, off to another thread");
    // ...
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*sic 5

更好的解决方案是使用F#,你想要真正的不变性吗?

  • 那么,同样的问题也适用于C#,更是如此.我认为克里斯建议使用F#,至少那时不变性是该语言的主要部分,而在C#中它只是一个脚注. (2认同)
  • 我从不担心字符串类型.这是一个不寻常的类型,因为它是一个看起来像值的引用类型,但这只是程序员使用字符串的反映:当我们更改它们时,我们想要新的(不可变的),但是当我们传递它们时,我们希望通过引用传递它们.我认为他们做了正确的事情,使它成为一个看似价值的不可变类型.(说实话,对于不安全的代码,.NET字符串可以变异,但这是一个极端的情况.) (2认同)

Eri*_*ich 2

就我个人而言,我并不真正了解此问题的任何第三方或以前的解决方案,因此如果我涉及旧问题,我深表歉意。但是,如果我要为我正在从事的项目实施某种不变性标准,我会从这样的开始:

\n\n
public interface ISnaphot<T>\n{\n    T TakeSnapshot();\n}\n\npublic class Immutable<T> where T : ISnaphot<T>\n{\n    private readonly T _item;\n    public T Copy { get { return _item.TakeSnapshot(); } }\n\n    public Immutable(T item)\n    {\n        _item = item.TakeSnapshot();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

该接口的实现类似于:

\n\n
public class Customer : ISnaphot<Customer>\n{\n    public string Name { get; set; }\n    private List<string> _creditCardNumbers = new List<string>();\n    public List<string> CreditCardNumbers { get { return _creditCardNumbers; } set { _creditCardNumbers = value; } }\n\n    public Customer TakeSnapshot()\n    {\n        return new Customer() { Name = this.Name, CreditCardNumbers = new List<string>(this.CreditCardNumbers) };\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

客户端代码将类似于:

\n\n
    public void Example()\n    {\n        var myCustomer = new Customer() { Name = "Erik";}\n        var myImmutableCustomer = new Immutable<Customer>(myCustomer);\n        myCustomer.Name = null;\n        myCustomer.CreditCardNumbers = null;\n\n        //These guys do not throw exceptions\n        Console.WriteLine(myImmutableCustomer.Copy.Name.Length);\n        Console.WriteLine("Credit card count: " + myImmutableCustomer.Copy.CreditCardNumbers.Count);\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

明显的缺陷是,实现的好坏取决于 的ISnapshot实现的客户端TakeSnapshot,但至少它会标准化事物,并且如果您遇到与可疑的可变性相关的问题,您会知道去哪里搜索。潜在的实现者也有责任认识到他们是否可以提供快照不变性并且不实现该接口,如果不能(即该类返回对不支持任何类型的克隆/复制的字段的引用,因此不能快照版)。

\n\n

正如我所说,这是一个开始\xe2\x80\x94我可能如何开始\xe2\x80\x94当然不是一个最佳解决方案或一个完成的、完美的想法。从这里,我会看到我的用法是如何演变的,并相应地修改这种方法。但是,至少在这里我知道我可以定义如何使某些东西不可变并编写单元测试以确保它是不可变的。

\n\n

我意识到这与实现对象复制相差不远,但它标准化了复制与不可变性。在代码库中,您可能会看到 的一些实现者ICloneable、一些复制构造函数和一些显式复制方法,甚至可能在同一个类中。定义这样的东西告诉你,意图与不变性具体相关\xe2\x80\x94我想要一个快照而不是一个重复的对象,因为我碰巧想要n个以上的该对象。该类Immtuable<T>还集中了不变性和副本之间的关系;如果您稍后想要以某种方式进行优化,例如缓存快照直到变脏,则无需在复制逻辑的所有实现器中执行此操作。

\n