2009-08-20 18 views
7

Tengo el siguiente intento de un objeto inmutable:Los objetos inmutables con inicializadores de objeto

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; } 
} 

Entonces, trato de usarlo de esta manera:

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

Pero el inicializador de objeto falla porque Name ' s setter es privado. ¿Hay alguna forma de evitar esto o tengo que elegir entre uno u otro?

+0

He actualizado mi respuesta para incluir mi versión del patrón del generador. Espero eso ayude. –

Respuesta

20

No puede utilizar inicializadores de objetos con objetos inmutables. Requieren propiedades configurables.

Un objeto inmutable implica "no cambia después de la creación". Hacer Name un parámetro constructor expresa claramente ese principio.

Si el objeto se vuelve demasiado complicado para un constructor comprensible, también se puede utilizar el Builder. En general, el propio constructor tendrá propiedades mutables (que puede usar en los inicializadores de objetos), y su método .Build() creará la instancia real.

EDIT (OP): Voy a agregar mi propio ejemplo de un generador que cociné aquí, luego acepto esta respuesta ya que propone una solución razonable.

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; } 
} 

entonces usted puede construir una instancia de ella con lo siguiente:

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

EDITAR: Esta es mi opinión sobre el patrón:

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(); 
} 

Aquí está su ejemplo, como implementado con Builder<T>. Se aprovecha de las reglas de alcance de tipos anidados para acceder a la incubadora privada:

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; } 
     } 
    } 
} 

Tiene una conversión implícita al tipo de destino, lo que le permite hacer esto:

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

O esto:

public void Foo(MyObject myObject) 

// ... 

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

Agregó un ejemplo, espero que no te importe. Avíseme si lo hice mal (dedos cruzados). –

+0

Dado que tengo alrededor de 20 propiedades, creo que este es el camino que seguiré. –

+0

Como siempre, alguien con experiencia tiene algunas ideas mejores que las mías. Si pudiera darle otro +1 para el operador de conversión implícita, lo haría. –

4

Haga que el colocador sea público o cree una sobrecarga de constructor para establecer la propiedad Nombre.

class MyObject{ 

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

    public string Name { get; private set; } 
} 

La llamada

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

es equivalente a

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

Para que sea realmente inmutable crear un campo para la propiedad Nombre y que sea de sólo lectura. Por lo que la propiedad Nombre sólo se puede ajustar mediante el constructor y no se puede cambiar durante el tiempo de ejecución.

class MyObject{ 

    private readonly string _name; 

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

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

La primera opción significa que ya no es inmutable. El segundo significa que no puedo usar los inicializadores de objetos. ¿Significa esto que tengo que elegir de una manera u otra? –

+2

Sí, el inicializador de objetos es "compilación mágica" para acortar la forma de crear un objeto y establecer algunas de sus propiedades – Jehof

+0

Estos son solo objetos de almacenamiento de datos. No habrá ningún método real en ellos más allá de los constructores –

8

Tiene que establecer la propiedad en el constructor, y usted no necesita una variable local independiente para el 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; } 
} 

Creación:

MyObject o1 = new MyObject("foo"); 
3

no se puede utilizar los inicializadores de objetos y tienen un objeto inmutable, ya que los inicializadores de objetos requieren que los establecedores de propiedades sean públicos. Los public setters significan que no será inmutable.

La única forma de evitar eso sería para simular la inmutabilidad, lanzando excepciones en la incubadora propiedad después de que ya han sido llamados una vez.

Personalmente no creo que sería un buen diseño, y me pregunta de por qué estás tan interesado en el uso de inicializadores de objeto, en lugar de constructores. La sintaxis es casi idéntica.

+1

Casi idéntico, pero entre otras cosas, los inicializadores de objeto permiten parámetros de constructor totalmente opcionales, algo que no obtendremos hasta C# 4.0 con sus parámetros opcionales y nombrados (¡por fin!) –

+0

En rigor, eso no es cierto. No permiten para nada los parámetros opcionales del consuter, simplemente usan una sintaxis que es similar en apariencia a un constructor. –

Cuestiones relacionadas