2011-11-27 12 views
6

Vorrei imporre una struttura per essere sempre valida per un determinato contratto, applicata dal costruttore. Tuttavia, il contratto viene violato dall'operatore default.Come posso applicare un contratto all'interno di una struttura

Si consideri il seguente, ad esempio:

struct NonNullInteger 
{ 
    private readonly int _value; 

    public int Value 
    { 
     get { return _value; } 
    } 

    public NonNullInteger(int value) 
    { 
     if (value == 0) 
     { 
      throw new ArgumentOutOfRangeException("value"); 
     } 

     _value = value; 
    } 
} 

// Somewhere else: 
var i = new NonNullInteger(0); // Will throw, contract respected 
var j = default(NonNullInteger); // Will not throw, contract broken 

Come una soluzione Ho cambiato il mio struct a una classe in modo da poter garantire il costruttore è sempre chiamato durante l'inizializzazione di una nuova istanza. Ma mi chiedo, c'è assolutamente non c'è modo di ottenere lo stesso comportamento con una struct?

risposta

3

Un approccio è quello di organizzare le cose in modo che il valore predefinito soddisfa il contratto:

struct NonNullInteger 
{ 
    private readonly int _valueMinusOne; 

    public int Value 
    { 
     get { return _valueMinusOne + 1; } 
    } 

    public NonNullInteger(int value) 
    { 
     if (value == 0) 
     { 
      throw new ArgumentOutOfRangeException("value"); 
     } 

     _valueMinusOne = value - 1; 
    } 
} 
+0

Questo è in realtà una buona soluzione per soddisfare la lettera del contratto, anche se sarebbe anche necessario verificare per int.MinValue. Non consiglierei di creare questa struttura, ma, se davvero volessi farlo, questa è probabilmente la soluzione più pulita. –

+0

+1 Trucco intelligente. Può essere molto più difficile per i contratti più complessi. –

6

Non vedo come si potrebbe fare questo, perché, a differenza di una classe, a struct always has a default parameterless constructor; dato il modo in cui è scritta la struct, non è possibile impedire il valore di 0:

Le strutture non possono contenere costruttori senza parametri espliciti. Stringa I membri vengono automaticamente inizializzati ai loro valori predefiniti.

1

In questo caso è preferibile una classe immutabile, poiché lo stato predefinito non è valido. È un po 'più costoso per quanto riguarda l'utilizzo della memoria, ma ciò non dovrebbe importare a meno che non si utilizzi un numero molto elevato di questi. In realtà, un vincolo di "numero diverso da zero" è probabilmente gestito meglio a livello contrattuale per ciascun metodo, piuttosto che inserito in una classe.

Se si vuole veramente far rispettare il contratto, inserire l'eccezione nel valore getter invece del costruttore. Quindi il contratto prevede il lancio di un'eccezione se mai contiene un valore 0; l'unico vero vantaggio qui è che non usi mai silenziosamente un valore zero. Il rovescio della medaglia è che ora hai un confronto ogni volta che usi il valore.

1

Anche se non è possibile ottenere esattamente quello che volete è possibile convalidare nel getter:

public int Value 
{ 
    get 
    { 
     if (_value == 0) 
      throw new InvalidOperationException("Use of uninitialized struct"); 
     return _value; 
    } 
}