2009-08-20 17 views
7

Ho il seguente tentativo di un oggetto immutabile:oggetti immutabili con inizializzatori oggetto

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

Quindi, cerco di usarlo in questo modo:

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

Ma l'initialiser oggetto fallisce perché Name ' s setter è privato. C'è un modo per aggirare questo, o devo scegliere tra uno o l'altro?

+0

Ho aggiornato la mia risposta per includere la mia versione del modello di builder. Spero che sia d'aiuto. –

risposta

20

Non è possibile utilizzare gli inizializzatori di oggetto con oggetti immutabili. Richiedono proprietà modificabili.

Un oggetto immutabile implica "non cambia dopo la creazione". Rendere Name un parametro del costruttore esprime perfettamente questo principio.

Se l'oggetto diventa troppo complicato per un costruttore comprensibile, è anche possibile utilizzare il modello Generatore. Generalmente, il builder avrà proprietà mutabili (che è possibile utilizzare negli inizializzatori di oggetti) e il suo metodo .Build() creerà l'istanza effettiva.

MODIFICA (OP): Aggiungo il mio esempio di generatore che ho preparato qui, quindi accetto questa risposta poiché propone una soluzione ragionevole.

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

È possibile quindi costruire un'istanza con il seguente:

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

EDIT: questo è il mio introito sul modello:

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

Ecco il vostro esempio come implementato con Builder<T>. Si avvale delle regole di scoping di tipi nidificati per accedere al setter privato:

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

Ha una conversione implicita per il tipo di destinazione, che consente di fare questo:

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

O questo:

public void Foo(MyObject myObject) 

// ... 

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

Aggiunto un esempio, spero non ti dispiaccia. Fammi sapere se ho sbagliato (incrociamo le dita). –

+0

Dato che ho circa 20 proprietà, penso che questo sia il modo in cui andrò. –

+0

Come al solito, qualcuno esperto ha alcune idee migliori delle mie. Se potessi darti un altro +1 per l'operatore di conversione implicito, lo farei. –

4

Rendi pubblico il setter o crea un overload di costruttore per impostare la proprietà Nome.

class MyObject{ 

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

    public string Name { get; private set; } 
} 

La chiamata

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

è equivalente a

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

Per rendere davvero immutabile creare un campo per la proprietà Nome e renderlo di sola lettura. Quindi la proprietà Nome può essere impostata solo utilizzando il costruttore e non può essere modificata durante il runtime.

class MyObject{ 

    private readonly string _name; 

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

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

La prima opzione significa che non è più immutabile. Il secondo significa che non posso usare gli inizializzatori di oggetti. Questo significa che ho scelto in un modo o nell'altro? –

+2

Sì, l'inizializzatore dell'oggetto è "magia del compilatore" per abbreviare il modo in cui creare un oggetto e impostarne alcune proprietà. – Jehof

+0

Questi sono solo oggetti di archiviazione dati. Non ci saranno metodi effettivi su di essi oltre il costruttore (i) –

8

È necessario impostare la proprietà nel costruttore, e non hai bisogno di una variabile locale separato per l'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; } 
} 

creazione:

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

non è possibile utilizzare inizializzatori di oggetti e hanno un oggetto immutabile, poiché gli inizializzatori dell'oggetto richiedono che i setter della proprietà siano pubblici. Settori pubblici significa che non sarà immutabile.

L'unico modo per simulare l'immutabilità, lanciando eccezioni nel setter dopo che sono già stati chiamati una volta.

Personalmente non penso che sarebbe un buon progetto, e mi chiederei perché sei così appassionato di utilizzare gli inizializzatori di oggetti, invece di costruttori. La sintassi è quasi identica.

+1

Quasi identici, ma tra le altre cose, gli inizializzatori di oggetti consentono parametri di costruzione completamente opzionali, qualcosa che in realtà non otterremo fino a C# 4.0 con i suoi parametri opzionali e denominati (finalmente!) –

+0

Strettamente parlando, questo non è vero. Non consentono affatto parametri facoltativi del costruttore, usano solo una sintassi simile a un costruttore. –

Problemi correlati