将对象转换为不可变

时间:2013-04-30 01:00:30

标签: c# immutability

我最初创建的类不是不可变的,但现在我想要选择创建一个不可变的等效数据结构。例如,假装可变类:

namespace Utility
{
    public class bar
    {
        public string name { get; set; }
        public double weight { get; set; }
        public int age { get; set;}
        public List<...> friendInstances; //instantiated at run time
    }
}

  //and now I make a mutable class.

public class MemorySafe_bar
{
    private readonly string _name;
    private readonly double _weight;
    private readonly int _age;
    private readonly List<...> _friendInstances;

    public MemorySafe_bar(string name, double weight, int age,
         List<...> friend Inst)
    {
        _name = name;
        _weight = weight;
        _age = age;
        _friendInstances = Inst
    }
    //..getters would go here...

    function()
    {
      Utility.bar bar_ex = new bar();
      bar_ex.name = "Kathy";
      bar_ex.weight = 42.34;
      bar_ex.age = 10;
      bar_ex.List<...> friends = new List<...>();
      friends.Add(stuff);

      Utility.MemorySafe_bar = new MemorySafe_bar(
        bar_ex.name, bar_ex.weight, bar_ex.age, friends);
    }
}

我不相信将来可以改变这个可变对象。

1 个答案:

答案 0 :(得分:2)

如果你要求一个通用/可重用的方法将任何类包装成一个不可变版本,那么从一般意义上来说它实际上是不可能的。

如果特定类将其成员公开为virtualabstract(或interface),则可以创建不执行任何操作的实现(或抛出异常) )在制定者身上,但这通常是出乎意料的。

在你目前的情况下,我会首先更新构造函数以获取要包装的对象,或者通过静态工厂方法执行此操作。我还会存储friendInstances的本地副本,并返回一个只读的可枚举副本:

public class ReadOnlyBar
{
    public string name { get; private set; }
    public double weight { get; private set; }
    public int age { get; private set; }

    private readonly Friend[] _friendInstances;

    public IEnumerable<Friend> friendInstances
    {
        get
        {
            foreach(var friend in _friendInstances)
                yield return friend;
        }
    }

    public ReadOnlyBar(Bar bar)
    {
        this.name = bar.name;
        this.weight = bar.weight;
        this.age = bar.age;
        this._friendInstances = bar.friendInstances.ToArray();
    }
}

用法如:

Bar mutableBar = new mutableBar() { name="Kathy", .... };
ReadOnlyBar readonlyBar = new ReadOnlyBar(mutableBar);

我只使用属性而不是readonly字段保留不可变栏,只是为了尽可能地匹配原始Bar的API;这些可以很容易地切换回字段(这将有助于强化对类中的骨头编码的不变性)。您还可以轻松地将创建移动到静态工厂方法或扩展方法,这样您就可以使用:

Bar mutableBar = new mutableBar() { name="Kathy", .... };
ReadOnlyBar readonlyBar = ReadOnlyBar.Create(mutableBar);
//or
ReadOnlyBar readonlyBar = mutableBar.MakeReadOnly();

编辑:如果您想维护List<Friend>的大多数功能/成员并且不将其降级为IEnumerable,则可以使用另一个快速选项,您可以使用此选项:

public ReadOnlyCollection<Friend> friendInstances { get; private set; }

public ReadOnlyBar(Bar bar)
{
    //other initialization
    this.friendInstances = bar.friendInstances.ToList().AsReadOnly();
}

或者您甚至可以键入List<Friend>并在getter中返回内部列表的副本,但这可能会有点远,并且是一个令人困惑的属性“不可变”的对象类型。