2009-05-08 19 views
5

Ich mag mit folgendem Test.net Unveränderliche Objekte

[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"); 
    } 
} 

es auf meiner Code-Basis unveränderliche Regel erzwingen geht für:

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

aber nicht dafür:

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

Und hier geht die Frage "WTF?"

Antwort

3

Eine kleine Modifikation der Antworten an anderer Stelle. Der folgende Wert gibt nicht null zurück, wenn mindestens eine Eigenschaft mit einem geschützten oder öffentlichen Setter vorhanden ist. Beachten Sie, dass die Prüfung für GetSetMethod null (kein Setter) und den Test für IsPrivate (d. H. Nicht öffentlich oder geschützt) statt IsPublic (nur öffentlich) zurückgibt.

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

Dennoch, wie pointed out in Daniel Brückner's answer die Tatsache, dass eine Klasse keine öffentlich sichtbaren Eigenschaft Setter hat, ist eine notwendige, aber keine hinreichende Bedingung für die Klasse unveränderlich zu betrachten.

+0

Der Test an GetSetMethod (true)! = Null ist überflüssig weil CanWrite == true die Existenz eines Setter garantiert. –

+0

True, "s.GetSetMethod (true)! = Null" ist gleichbedeutend mit CanWrite ist wahr, so dass einer von ihnen redundant ist. Ich habe CanWrite entfernt, da ich explizite Informationen zum Testen auf Null geben möchte, bevor ich die IsPrivate-Eigenschaft dereferenziere. – Joe

1

Sicher ist es Ihre in der zweiten.

In der ersten Instanz kann eine Instanz der Klasse Entity ihre ID1-Eigenschaft von einer externen Klasse geschrieben haben.

In letzterem ist der Setter der Klasse Privat selbst und so kann nur innerhalb Entity aufgerufen werden (dh eine eigener Konstruktor/initializer)

Nehmen Sie die private aus Ihrem Setter in den zweiten und es soll pass

+0

ID1 ist ein Backing-Feld und keine Eigenschaft in der ersten Klasse und es ist schreibgeschützt über einen Getter nach außen. Wie kann es von einer externen Klasse zugänglich sein? –

+0

In seinem ursprünglichen Beispiel hatte er einen öffentlichen Setter auf dem ersten Grundstück. Er hat seitdem die Frage bearbeitet. http://stackoverflow.com/revisions/839066/list –

+0

Ich sehe. Danke Eoin. –

2

Ich denke der Grund ist, dass CanWrite sagt, dass es wahr zurückgibt, wenn es einen Setter gibt. Ein privater Setter ist auch ein Setter.

Was mich ein wenig überrascht ist, dass das erste passiert, da es einen öffentlichen Setter hat, also, wenn ich noch zu wenig auf cafeine bin, ist der Settercount 1, also sollte die Assert fehlschlagen. Beide sollten fehlschlagen, da CanWrite für beide einfach den Wert true zurückgibt. (und die Linq-Abfrage ruft einfach öffentliche Eigenschaften ab, einschließlich der ID, da sie öffentlich ist)

(bearbeiten) Ich sehe jetzt, dass Sie den Code der ersten Klasse geändert haben, so dass es keinen Setter mehr hat.

Also die Sache ist Ihre Annahme, dass CanWrite Accessoren der Setter-Methode betrachtet, das ist nicht der Fall. Sie tun sollten:

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

Ich habe gerade den Code ausprobiert, und ich stimme zu. Ich bekomme für beide Test-Klassen 1 Setter. –

+0

Wenn ich nicht auch zu wenig Kaffee habe, sehe ich den Setter im ersten Fall nicht. ID1 ist eine Membervariable, keine Eigenschaft, also hat sie keine "Setter" -Methode? –

+0

genau. Was TS stattdessen tun sollte, ist ein Aufruf von GetSetMethod() auf der Eigenschaftinfo und dann überprüfen, ob IsPublic für die zurückgegebene MethodInfo wahr ist. –

7

Das Problem ist, dass die Eigenschaft public ist - wegen der öffentlichen Getter - und es ist beschreibbar - wegen der privaten Setter. Sie müssen Ihren Test verfeinern.

Weiter möchte ich hinzufügen, dass Sie nicht die Unveränderlichkeit auf diese Weise garantieren können, da Sie private Daten in Methoden ändern können. Um Unveränderlichkeit zu gewährleisten, müssen Sie überprüfen, ob alle Felder schreibgeschützt deklariert sind und keine automatisch implementierten Eigenschaften vorhanden sind.

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

Mit dieser Erweiterung Methode können Sie leicht Typen testen

typeof(MyType).IsImmutable() 

und Instanzen

myInstance.IsImmutable() 

für ihre Unveränderlichkeit.

Hinweise

  • Mit Blick auf Instanzfelder können Sie beschreibbaren Eigenschaften haben, gewährleistet aber, dass es jetzt Felder, die geändert werden könnten.
  • Automatisch implementierte Eigenschaften werden den Immutability-Test aufgrund des privaten, anonymen Hintergrundfelds nicht wie erwartet ausführen.
  • Sie können die Felder readonly immer noch mit Reflektion ändern.
  • Vielleicht sollte man überprüfen, dass die Typen aller Felder auch unveränderlich sind, weil diese Objekte zum Zustand gehören.
  • Dies ist nicht möglich mit einem einfachen FiledInfo.FieldType.IsImmutable() für alle Felder wegen der möglichen Zyklen und weil die Basistypen veränderbar sind.
+0

Gibt es einen Grund, 'Object' und' Boolean' anstelle von 'object' und' bool' zu verwenden? –

+0

Sie sehen schöner aus (Syntaxhervorhebung und Großbuchstabe) und sie sind echte Typen und hängen nicht davon ab, was der Compiler für string, bool, int, object und all die anderen erzeugt (während ich weiß, dass der Compiler keine andere Wahl hat)). –

+0

Gibt es gute Gründe dagegen? –

3

Sie müssen wahrscheinlich

propertyInfo.GetSetMethod().IsPublic 

weil die Set-Methode und die get-Methode aufrufen, nicht den gleichen Zugriffsmodifikator haben, können Sie nicht auf dem Property sich verlassen können.

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 Das ist fast da. GetSetMethod() gibt jedoch null für private Setter oder Eigenschaften ohne Setter zurück. Sie müssen dies ändern, um auf Null zu testen. – Joe

+0

@Joe: Ok, gefixt, dann brauche ich das CanWrite nicht mehr –

+0

Du brauchst nicht an GetSetMethod(). Auch IsPublic, weil ProeprtyInfo.GetSetMethod() nur public set Methoden zurückgibt, wenn du nicht ProeprtyInfo aufruft. GetSetMethod (Boolean nicht öffentlich). –

1

Wenn ich Sie richtig verstanden habe, wollen Sie, dass die Entität unveränderlich ist. Wenn ja, dann sollte der Test auf

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

Die Assertion ist korrekt - der Typ ist unveränderlich, wenn setterCount == 0 wahr ist und die Assertion die Meldung "Unveränderliche Regel ist fehlerhaft" anzeigt, wenn dieser Test fehlschlägt. –

+0

Fixed, danke Daniel – SeeR

2

Ich schlage vor, in der Eigenschaft Zustand eine Änderung geändert werden:

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

Haben Sie versucht, es zu debuggen, welche Eigenschaften, deren CanWrite Eigenschaft true zu sehen werden von Ihrer LINQ-Abfrage zurückgegeben? Offensichtlich nimmt es Dinge auf, die Sie nicht erwarten. Mit diesem Wissen können Sie Ihre LINQ-Abfrage wählerischer gestalten, um so zu arbeiten, wie Sie es möchten. Außerdem stimme ich @Daniel Bruckner zu, dass Ihre Klasse nicht vollständig veränderbar ist, es sei denn, die Felder werden auch nur gelesen. Der Aufrufer kann eine Methode aufrufen oder einen Getter verwenden, der private Felder, die öffentlich als schreibgeschützt über Getter verfügbar gemacht werden, intern ändert. Dies würde die unveränderbare Regel durchbrechen, den Client überraschen und Probleme verursachen. Die Operationen am veränderbaren Objekt sollten keine Nebenwirkungen haben.

Verwandte Themen