具有对象初始化器的不可变对象

时间:2009-08-20 05:39:08

标签: c#

我对不可变对象进行了以下尝试:

class MyObject
{
    private static int nextId;

    public MyObject()
    {
        _id = ++nextId;
    }

    private int _id;
    public int Id { get { return _id; } }
    public string Name { get; private set; }
}

然后,我尝试使用它:

MyObject o1 = new MyObject { Name = "foo" };

但是对象初始化器失败了,因为Name的setter是私有的。有没有办法解决这个问题,还是我必须在其中一个选择?

4 个答案:

答案 0 :(得分:26)

您不能将对象初始值设定项与不可变对象一起使用。它们需要可设置的属性。

不可变对象意味着“创建后不会改变”。使Name构造函数参数巧妙地表达了这一原则。

如果对象对于可理解的构造函数来说太复杂,您还可以使用Builder模式。通常,构建器本身将具有可变属性(可以在对象初始化器中使用),并且其.Build()方法将创建实际实例。

编辑(OP):我将添加我自己在这里制作的构建器的示例,然后接受这个答案,因为它提出了合理的解决方案。

class MyObject
{
    public class Builder
    {
        public Builder()
        {
            // set default values
            Name = String.Empty;
        }

        public MyObject Build()
        {
            return new MyObject(Name);
        }
        public string Name { get; set; }
    }

    private static int nextId;

    protected MyObject(string name)
    {
        Id = ++nextId;
        Name = name;
    }

    public int Id { get; private set; }
    public string Name { get; private set; }
}

然后,您可以使用以下内容构建它的实例:

MyObject test = new MyObject.Builder { Name = "foo" }.Build();

编辑:这是我对模式的看法:

public abstract class Builder<T>
{
    public static implicit operator T(Builder<T> builder)
    {
        return builder.Build();
    }

    private bool _built;

    public T Build()
    {
        if(_built)
        {
            throw new InvalidOperationException("Instance already built");
        }

        _built = true;

        return GetInstance();
    }

    protected abstract T GetInstance();
}

以下是使用Builder<T>实施的示例。它利用嵌套类型的作用域规则来访问私有setter:

public class MyObject
{
    private static int nextId;

    protected MyObject()
    {
        Id = ++nextId;
    }

    public int Id { get; private set; }

    public string Name { get; private set; }

    public sealed class Builder : Builder<MyObject>
    {
        private MyObject _instance = new MyObject();

        protected override MyObject GetInstance()
        {
            // Validate properties here

            return _instance;
        }

        public string Name
        {
            get { return _instance.Name; }
            set { _instance.Name = value; }
        }
    }
}

它隐式转换为目标类型,允许您执行此操作:

MyObject myObject = new MyObject.Builder { Name = "Some name" };

或者这个:

public void Foo(MyObject myObject)

// ...

Foo(new MyObject.Builder { Name = "Some name" });

答案 1 :(得分:8)

您需要在构造函数中设置属性,并且您不需要为id添加单独的局部变量:

class MyObject {

   private static int nextId = 0;

   public MyObject(string name) {
      Id = ++nextId;
      Name = name;
   }

   public int Id { get; private set; }
   public string Name { get; private set; }
}

创建:

MyObject o1 = new MyObject("foo");

答案 2 :(得分:4)

将setter设为public或创建构造函数重载以设置Property Name

class MyObject{

    public MyObject(string name) {
        Name = name;
    }

    public string Name { get; private set; }
}

电话

MyObject o1 = new MyObject { Name = "foo" };

相当于

MyObject o1 = new MyObject();
o1.Name = "foo"; //This doesn´t work, cause the setter is private.

要使其真正不可变,请为属性 Name 创建一个字段,并使其成为只读状态。所以Property Name 只能使用构造函数设置,并且在运行时不能更改。

class MyObject{

    private readonly string _name;

    public MyObject(string name) {
        _name = name;
    }

    public string Name { 
      get { return _name; }
    }
}

答案 3 :(得分:3)

您不能使用对象初始值设定项并且具有不可变对象,因为对象初始值设定项要求属性设置器是公共的。公共制定者意味着它不会是不可变的。

唯一的方法就是模拟不变性,方法是在属性设置器中调用异常后再调用一次。

我个人认为这不是一个好的设计,我会质疑你为什么如此热衷于使用对象初始化器而不是构造器。语法几乎相同。