代码之家  ›  专栏  ›  技术社区  ›  Matthew Scharley

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

c#
  •  9
  • Matthew Scharley  · 技术社区  · 15 年前

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

    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 回复  |  直到 15 年前
        1
  •  25
  •   Bryan Watts    15 年前

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

    一个不变的对象意味着“在创建之后不会改变”。制作 Name 构造器参数巧妙地表达了这一原则。

    如果对象对于可理解的构造函数来说过于复杂,您也可以使用构建器模式。通常,生成器本身将具有可变属性(可以在对象初始值设定项中使用),其.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" });
    
        2
  •  8
  •   Guffa    15 年前

    您需要在构造函数中设置属性,并且不需要为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");
    
        3
  •  4
  •   Jehof    15 年前

    将setter设置为公共的或创建构造函数重载以设置属性 名字 .

    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.
    

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

    class MyObject{
    
        private readonly string _name;
    
        public MyObject(string name) {
            _name = name;
        }
    
        public string Name { 
          get { return _name; }
        }
    }
    
        4
  •  3
  •   David Wengier    15 年前

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

    唯一的解决方法是模拟不可变性,在调用一次异常之后在属性设置器中抛出异常。

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