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

Mat*_*ley 11 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; }
}
Run Code Online (Sandbox Code Playgroud)

然后,我试着像这样使用它:

MyObject o1 = new MyObject { Name = "foo" };
Run Code Online (Sandbox Code Playgroud)

但是对象初始化器失败了,因为它Name的setter是私有的.有没有办法解决这个问题,还是我必须在一个或另一个之间做出选择?

Bry*_*tts 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; }
}
Run Code Online (Sandbox Code Playgroud)

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

MyObject test = new MyObject.Builder { Name = "foo" }.Build();
Run Code Online (Sandbox Code Playgroud)

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

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();
}
Run Code Online (Sandbox Code Playgroud)

以下是您实施的示例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; }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它具有对目标类型的隐式转换,允许您执行以下操作:

MyObject myObject = new MyObject.Builder { Name = "Some name" };
Run Code Online (Sandbox Code Playgroud)

或这个:

public void Foo(MyObject myObject)

// ...

Foo(new MyObject.Builder { Name = "Some name" });
Run Code Online (Sandbox Code Playgroud)

  • 这非常整洁,让我想知道为什么C#在默认情况下不允许它(对象初始化程序应该不仅仅是语法糖)。 (2认同)

Guf*_*ffa 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; }
}
Run Code Online (Sandbox Code Playgroud)

创建:

MyObject o1 = new MyObject("foo");
Run Code Online (Sandbox Code Playgroud)