2009-05-08 14 views
5

voglio rispettare la mia base di codice regola immutabile con i seguenti test dioggetti immutabili .net

[TestFixture] 
public class TestEntityIf 
{ 
    [Test] 
    public void IsImmutable() 
    { 
     var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.CanWrite 
      select s) 
       .Count(); 

     Assert.That(setterCount == 0, Is.True, "Immutable rule is broken"); 
    } 
} 

Passa per:

public class Entity 
{ 
    private int ID1; 
    public int ID 
    { 
     get { return ID1; } 
    } 
} 

ma non lo fa per questo:

public class Entity 
{ 
    public int ID { get; private set; } 
} 

E qui va la domanda "WTF?"

risposta

3

Una piccola modifica alle risposte pubblicate altrove. Quanto segue restituirà un valore diverso da zero se esiste almeno una proprietà con un setter protetto o pubblico. Notare il controllo per GetSetMethod che restituisce null (nessun setter) e il test per IsPrivate (cioè non pubblico o protetto) piuttosto che IsPublic (solo pubblico).

var setterCount = 
      (from s in typeof(Entity).GetProperties(
       BindingFlags.Public 
       | BindingFlags.NonPublic 
       | BindingFlags.Instance) 
      where 
       s.GetSetMethod(true) != null // setter available 
       && (!s.GetSetMethod(true).IsPrivate) 
      select s).Count(); 

Tuttavia, come pointed out in Daniel Brückner's answer, il fatto che una classe non ha property setter pubblicamente visibili è necessaria, ma non sufficiente per la classe da considerare immutabile.

+0

Il test s.GetSetMethod (true)! = Null è superfluo perché CanWrite == true garantisce l'esistenza di un setter. –

+0

Vero, "s.GetSetMethod (true)! = Null" equivale a CanWrite che è true, quindi uno di questi è ridondante. Ho rimosso CanWrite perché voglio essere esplicito sul test per null prima di dereferenziare la proprietà IsPrivate. – Joe

1

Sicuramente è il tuo private set nel secondo.

Nel primo, un'istanza della classe Entity può avere la proprietà ID1 scritta da una classe esterna.

In quest'ultimo, il setter è privato alla classe stessa e così può essere chiamato solo dall'interno Entity (vale a dire che il proprio costruttore/inizializzazione)

Prendere la private dal vostro setter nel secondo e dovrebbe passare

+0

ID1 è un campo di supporto e non una proprietà nella prima classe ed è in sola lettura tramite un getter al mondo esterno. Come può essere accessibile da una classe esterna? –

+0

Nel suo esempio originale, ha avuto un setter pubblico sulla prima proprietà. Da allora ha modificato la domanda. http://stackoverflow.com/revisions/839066/list –

+0

Vedo. Grazie Eoin. –

2

Penso che la ragione è che CanWrite dice che restituisce true se c'è un setter. Un setter privato è anche un setter.

Quello che mi sorprende un po 'è che il primo passa, in quanto ha un setter pubblico, quindi a meno che non sia ancora troppo basso su cafeine, il settercount è 1 quindi l'asserzione dovrebbe fallire. Entrambi dovrebbero fallire, dato che CanWrite restituisce semplicemente la verità per entrambi. (e la query linq richiama semplicemente proprietà pubbliche, incluso l'ID, poiché è pubblica)

(modifica) Ora vedo che hai cambiato il codice della prima classe, quindi non ha più un setter.

Quindi la tua ipotesi è che CanWrite guardi ai metodi di accesso al metodo setter, non è così. Si dovrebbe fare:

var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.GetSetMethod().IsPublic 
      select s) 
       .Count(); 
+0

Ho appena provato il codice e sono d'accordo. Ricevo un conteggio di 1 setter per entrambe le classi di test. –

+0

A meno che non sia troppo basso anche con la caffeina, non vedo il setter nel primo caso. ID1 è una variabile membro, non una proprietà quindi non ha un metodo "setter"? –

+0

esattamente. Quello che TS dovrebbe fare è una chiamata a GetSetMethod() sul propertyinfo, e quindi controlla se IsPublic è vero sul MethodInfo restituito. –

7

Il problema è, che la proprietà è pubblica - a causa del getter pubblico - ed è scrivibile - a causa del setter privato. Dovrai affinare il tuo test.

Inoltre, desidero aggiungere che non è possibile garantire l'immutabilità in questo modo poiché è possibile modificare i dati privati ​​all'interno dei metodi. Per garantire l'immutabilità, è necessario verificare che tutti i campi siano dichiarati in sola lettura e che non vi siano proprietà autoattive.

public static Boolean IsImmutable(this Type type) 
{ 
    const BindingFlags flags = BindingFlags.Instance | 
           BindingFlags.NonPublic | 
           BindingFlags.Public; 

    return type.GetFields(flags).All(f => f.IsInitOnly); 
} 

public static Boolean IsImmutable(this Object @object) 
{ 
    return (@object == null) || @object.GetType().IsImmutable(); 
} 

Con questo metodo di estensione si può facilmente verificare tipi

typeof(MyType).IsImmutable() 

e le istanze

myInstance.IsImmutable() 

per la loro immutabilità.

Note

  • Guardando campi di istanza permette di avere le proprietà scrivibili, ma assicura che ora ci sono campi che possono essere modificati.
  • Le proprietà di implementazione automatica non supereranno il test di immutabilità come previsto a causa del campo di backing privato e anonimo.
  • È ancora possibile modificare i campi readonly utilizzando la riflessione.
  • Forse si dovrebbe controllare che anche i tipi di tutti i campi siano immutabili, perché questi oggetti appartengono allo stato.
  • Questo non può essere eseguito con un semplice FiledInfo.FieldType.IsImmutable() per tutti i campi a causa di possibili cicli e poiché i tipi di base sono modificabili.
+0

Qualche motivo per usare 'Object' e' Boolean' invece di 'object' e' bool'? –

+0

Sono più belli (evidenziazione della sintassi e maiuscole e minuscole) e sono tipi reali e non dipendono da ciò che il compilatore genera per stringa, bool, int, oggetto e tutti gli altri (mentre so che il compilatore non ha scelta). –

+0

Ci sono dei buoni motivi per non farlo? –

3

Probabilmente necessario chiamare

propertyInfo.GetSetMethod().IsPublic 

perché il set-metodo e il metodo get-non hanno lo stesso modificatore di accesso, non si può contare sul PropertyInfo stesso.

var setterCount = 
     (from s in typeof (Entity).GetProperties(
      BindingFlags.Public 
      | BindingFlags.NonPublic 
      | BindingFlags.Instance) 
     where 
      s.GetSetMethod() != null  // public setter available 
     select s) 
+0

+1 Questo è quasi arrivato. Ma GetSetMethod() restituirà null per setter privati ​​o proprietà senza setter. È necessario modificare questo per verificare null. – Joe

+0

@Joe: Ok, corretto, quindi non ho più bisogno di CanWrite –

+0

Non è necessario s.GetSetMethod(). IsPublic, perché ProeprtyInfo.GetSetMethod() restituisce solo metodi set pubblici a meno che non si chiami ProeprtyInfo. GetSetMethod (Boolean nonPublic). –

1

Se ho capito bene, vuoi che Entity sia immutabile. Se è così, allora il test deve essere modificato in

var setterCount = (from s in typeof(string).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(p => p.GetSetMethod()) 
    where s != null && s.IsPublic 
    select s).Count(); 

Assert.That(setterCount == 0, Is.True, "Immutable rule is broken"); 
+0

L'asserzione è corretta - il tipo è immutabile se setterCount == 0 è true e l'asserzione mostrerà il messaggio "La regola immutabile è rotta" se questo test fallisce. –

+0

Risolto, grazie Daniel – SeeR

2

suggerisco un cambiamento nella condizione proprietà per:

s.CanWrite && s.GetSetMethod().IsPublic 
0

Hai provato a eseguire il debug per vedere quali proprietà la cui proprietà CanWrite è vero vengono restituiti dalla query LINQ? Apparentemente, sta raccogliendo cose che non ti aspetti. Con questa conoscenza, puoi rendere la tua query LINQ più selettiva per funzionare nel modo desiderato. Inoltre, sono d'accordo con @Daniel Bruckner che la tua classe non è completamente modificabile a meno che i campi non siano anche di sola lettura. Il chiamante può chiamare un metodo o utilizzare un getter che può modificare internamente i campi privati ​​che vengono esposti pubblicamente come readonly via Getter e ciò infrange la regola immutabile, sorprende il cliente e causa problemi. Le operazioni su oggetti mutabili non dovrebbero avere effetti collaterali.

Problemi correlati