2010-04-27 16 views
13

Ich habe eine Klasse mit verschiedenen öffentlichen Eigenschaften, die ich Benutzern über ein Eigenschaftenraster bearbeiten kann. Für die Persistenz wird diese Klasse auch über DataContractSerializer zu/von einer XML-Datei serialisiert/deserialisiert.Wie kann ich eine schreibgeschützte Version einer Klasse erstellen?

Manchmal möchte ich Benutzer in der Lage sein, Änderungen, die sie an einer Instanz der Klasse vorgenommen haben, zu speichern (serialisieren). Zu anderen Zeiten möchte ich dem Benutzer nicht erlauben, ihre Änderungen zu speichern und stattdessen alle Eigenschaften im Eigenschaftenraster als schreibgeschützt anzeigen. Ich möchte Benutzern nicht erlauben, Änderungen vorzunehmen, die sie nie später speichern können. Ähnlich wie MS Word es Benutzern ermöglicht, Dokumente zu öffnen, die gerade von jemand anderem geöffnet sind, aber nur als schreibgeschützt.

Meine Klasse verfügt über eine boolesche Eigenschaft, die festlegt, ob die Klasse schreibgeschützt sein soll, aber ist es möglich, diese Eigenschaft zu verwenden, um den Klasseneigenschaften zur Laufzeit dynamisch schreibgeschützte Attribute hinzuzufügen? Wenn nicht, was ist eine alternative Lösung? Sollte ich meine Klasse in eine schreibgeschützte Wrapper-Klasse einbinden?

Antwort

16

Unveränderbarkeit ist ein Bereich, in dem C# noch verbessert werden kann. Während einfache unveränderliche Typen mit readonly Eigenschaften zu schaffen ist möglich, wenn Sie anspruchsvollere Steuerung müssen über, wenn Typ wandelbar sind Sie läuft in Hindernisse starten.

Es gibt drei Möglichkeiten, die Sie haben, je nachdem, wie stark müssen Sie „erzwingen“ read-only Verhalten:

  1. Verwenden Sie eine Nur-Lese-Flag in Ihrer Art (wie Sie tun) und lassen Sie Eigenschaften der Anrufer nicht versucht, die Verantwortung zu ändern auf dem Typ - wenn ein Schreib versucht wird, eine Ausnahme werfen.

  2. Erstellen Sie eine schreibgeschützte Schnittstelle und lassen Sie Ihren Typ implementieren. Auf diese Weise können Sie den Typ über diese Schnittstelle an Code übergeben, der nur Lesevorgänge ausführen soll.

  3. Erstellen Sie eine Wrapper-Klasse, die Ihren Typ aggregiert und nur Lesevorgänge ermöglicht.

Die erste Option ist oft die einfachste, dass es weniger Refactoring bestehenden Code erfordern kann, bietet aber die geringste Möglichkeit für den Autor eines Typs, die Verbraucher zu informieren, wenn eine Instanz unveränderlich vs ist, wenn es nicht. Diese Option bietet auch die geringste Unterstützung des Compilers bei der Erkennung unangemessener Verwendung - und weist die Fehlererkennung der Laufzeit zu.

Die zweite Option ist praktisch, da die Implementierung einer Schnittstelle ohne großen Refaktorierungsaufwand möglich ist. Leider können Anrufer immer noch zu dem zugrunde liegenden Typ wechseln und versuchen, dagegen zu schreiben. Häufig wird diese Option mit einem schreibgeschützten Flag kombiniert, um sicherzustellen, dass die Unveränderlichkeit nicht verletzt wird.

Die dritte Option ist die stärkste, soweit die Durchsetzung geht, aber es kann zu einer Duplizierung von Code führen und ist eher ein Refactoring-Aufwand. Oft ist es sinnvoll, Option 2 und 3 zu kombinieren, um die Beziehung zwischen dem schreibgeschützten Wrapper und dem veränderlichen Typ polymorph zu gestalten.

Persönlich neige ich dazu, die dritte Option zu bevorzugen, wenn ich neuen Code schreibe, wo ich erwarte, um Unveränderlichkeit durchzusetzen. Ich mag die Tatsache, dass es unmöglich ist, den unveränderlichen Wrapper "wegzuwerfen", und es erlaubt Ihnen oft zu vermeiden, unordentliche Wenn-Nur-Lesen-Wurf-Ausnahme-Prüfungen in jeden Setter zu schreiben.

+0

Danke! Leider funktioniert Option 2 bei mir nicht, da das Eigenschaftsraster eine Reflektion verwendet, um herauszufinden, welche realen Eigenschaften hinter einer Schnittstelle tatsächlich nicht schreibgeschützt sind. Es scheint also, dass Option 3 die beste Antwort für mich ist. Obwohl ich, wie Sie Ihren Vorschlag tue Optionen 2 zu kombinieren und 3. –

+0

Ich kann nicht ausdrücken, wie diese Antwort eine der prägnantesten und vollständig die ich im Internet gefunden. –

1

Ich würde eine Wrapper-Klasse verwenden, die alles schreibgeschützt hält. Dies ist für Skalierbarkeit, Zuverlässigkeit und allgemeine Lesbarkeit.

Ich sehe keine anderen Methoden, dies zu tun, die die oben genannten drei Vorteile sowie etwas mehr bieten. Verwende definitiv einen Wrapperkurs hier meiner Meinung nach.

2

Warum nicht so etwas wie:

private int someValue; 
public int SomeValue 
{ 
    get 
    { 
     return someValue; 
    } 
    set 
    { 
     if(ReadOnly) 
       throw new InvalidOperationException("Object is readonly"); 
     someValue= value; 
    } 
+1

Weil es (wie erwartet) nicht für Referenztypen funktioniert. Ihr Code entspricht dem Nur-Lese-Schlüsselwort, nur dass es unklarer ist. – greenoldman

+0

Warum würde es dem Schlüsselwort readonly entsprechen? Das ist dynamisch, das Schlüsselwort readonly ist es nicht. –

0

wäre so etwas wie diese Hilfe:

class Class1 
{ 
    private bool _isReadOnly; 

    private int _property1; 
    public int Property1 
    { 
     get 
     { 
      return _property1; 
     } 
     set 
     { 
      if (_isReadOnly) 
       throw new Exception("At the moment this is ready only property."); 
      _property1 = value; 
     } 
    } 
} 

Sie müssen Ausnahmen fangen, wenn Eigenschaften festlegen.

Ich hoffe, das ist etwas, was Sie suchen.

+0

Das würde immer Ausnahmen auslösen, Sie sollten niemals eine Instanz von Exception werfen. –

+0

@Oskar Wahr, ich habe es geändert. – user218447

0

Sie können keine Kompilierung-Kontrollen (wie mit dem Stichwort gegeben readonly) durch eine Eigenschaft ändert zur Laufzeit schreibgeschützt. Es gibt also keinen anderen Weg, manuell zu prüfen und eine Ausnahme auszulösen.

Aber wahrscheinlich ist es besser, den Zugriff auf die Klasse neu zu gestalten. Erstellen Sie zum Beispiel eine "Writer-Klasse", die prüft, ob die zugrunde liegende "Datenklasse" aktuell geschrieben werden kann oder nicht.

0

Sie können PostSharp verwenden, um OnFieldAccessAspect zu erstellen, das keinen neuen Wert an ein Feld übergibt, wenn _readOnly auf true festgelegt wird. Mit Seitencode ist die Wiederholung weg und es wird kein Feld vergessen.

1

Wenn Sie eine Bibliothek erstellen, ist es möglich, eine öffentliche Schnittstelle mit einer privaten/internen Klasse zu definieren. Jede Methode, die eine Instanz Ihrer schreibgeschützten Klasse an einen externen Benutzer zurückgeben muss, sollte stattdessen eine Instanz der schreibgeschützten Schnittstelle zurückgeben. Nun ist es nicht möglich, auf einen konkreten Typ umzustoßen, da der Typ nicht öffentlich zugänglich ist.

Utility Library

public interface IReadOnlyClass 
{ 
    string SomeProperty { get; } 
    int Foo(); 
} 
public interface IMutableClass 
{ 
    string SomeProperty { set; } 
    void Foo(int arg); 
} 

Ihre Bibliothek

internal MyReadOnlyClass : IReadOnlyClass, IMutableClass 
{ 
    public string SomeProperty { get; set; } 
    public int Foo() 
    { 
     return 4; // chosen by fair dice roll 
        // guaranteed to be random 
    } 
    public void Foo(int arg) 
    { 
     this.SomeProperty = arg.ToString(); 
    } 
} 
public SomeClass 
{ 
    private MyThing = new MyReadOnlyClass(); 

    public IReadOnlyClass GetThing 
    { 
     get 
     { 
      return MyThing as IReadOnlyClass; 
     } 
    } 
    public IMutableClass GetATotallyDifferentThing 
    { 
     get 
     { 
      return MyThing as IMutableClass 
     } 
    } 
} 

Jetzt kann jeder, der SomeClass verwendet wird zurückbekommen, was wie zwei verschiedene Objekte aussieht. Natürlich können sie Reflexion verwenden, um die zugrunde liegenden Typen zu sehen, die ihnen sagen würden, dass dies wirklich das gleiche Objekt mit dem gleichen Typ ist. Aber die Definition dieses Typs ist privat in einer externen Bibliothek. An dieser Stelle ist es technisch noch möglich, an die Definition heranzukommen, aber es erfordert Heavy Wizardry abzuziehen.

Je nach Projekt könnten Sie die oben genannten Bibliotheken zu einem kombinieren. Es gibt nichts, was das verhindert; Fügen Sie den obigen Code nicht in die DLL ein, für die Sie die Berechtigungen einschränken möchten.

Kredit zu XKCD für die Kommentare.

Verwandte Themen