2013-06-24 9 views
6

Für einige Klassen, im Idealfall, möchte ich spezielle benannte Instanzen, ähnlich wie "null" erstellen. Soweit ich weiß, das ist nicht möglich, so stattdessen ich schaffen statische Instanzen der Klasse, mit einem statischen Konstruktor, ähnlich wie diese:C# Wie erstelle ich spezielle Instanzen einer Klasse?

public class Person 
{ 
    public static Person Waldo; // a special well-known instance of Person 
    public string name; 
    static Person() // static constructor 
    { 
     Waldo = new Person("Waldo"); 
    } 
    public Person(string name) 
    { 
     this.name = name; 
    } 
} 

Wie Sie sehen können, ist Person.Waldo eine spezielle Instanz Die Klasse Person, die ich erstellt habe, weil es in meinem Programm viele andere Klassen gibt, die sich auf diese spezielle, bekannte Instanz beziehen könnten.

Der Nachteil der Implementierung dieser Methode ist, dass ich keine Möglichkeit kenne, alle Eigenschaften von Person.Waldo unveränderlich zu machen, während alle Eigenschaften einer "normalen" Person-Instanz änderbar sein sollten. Immer wenn ich versehentlich ein Person-Objekt habe, das sich auf Waldo bezieht, und ich achtlos nachschaue, ob es sich auf Waldo bezieht, dann klopfe ich aus Versehen an Waldos Grundstück.

Gibt es einen besseren Weg, oder sogar einige zusätzliche alternative Möglichkeiten, um spezielle bekannte Instanzen einer Klasse zu definieren?

Die einzige Lösung, die ich jetzt kenne, ist die Umsetzung & Set-Accessoren, und überprüfen Sie "ob (das == Waldo) werfen neue ..." auf jeden Satz. Während dies funktioniert, gehe ich davon aus, dass C# eine bessere Arbeit als ich leisten kann. Wenn ich nur einige C# -Wege finden kann, um alle Eigenschaften von Waldo readonly (außer während des statischen Konstruktors) zu machen.

+0

Könnten Sie nicht einfach den Accessor in Ihrer Waldo-Klasse verwenden und den Mutator nicht verwenden? – Brian

+0

Keine direkte Antwort auf Ihre Frage, aber eine mögliche Richtung: http://msdn.microsoft.com/en-us/library/ms750509.aspx und http://stackoverflow.com/questions/263585/immutable-object- pattern-in-c-sharp-what-do-you-think – hatchet

+0

Sie könnten eine Schnittstelle 'IPerson 'definieren und' Waldo' zu einer Klasse machen, die 'IPerson' implementiert, auch' Person' implementiert sie.Dann müssen Sie "Waldo" unveränderlich machen, schauen Sie hier für weitere Informationen, wie Sie eine Klasse unveränderlich machen: http://stackoverflow.com/questions/352471/how-do-i-create-an-immutable-class –

Antwort

0

Vielen Dank für all Ihre Vorschläge - Hier ist die Lösung. Ich musste die Zeichenfolge virtuell machen, die Accessoren in einer öffentlichen abgeleiteten Klasse überschreiben und eine bool-Markierung verwenden, um eine Änderung zu erlauben.

Es ist wichtig zu beachten, dass "name" ein Referenztyp ist, und obwohl ich verhindert habe, zu ändern, was "name" bezieht, wenn es etwas anderes als eine Zeichenfolge wäre, wie eine Klasse, die ein Selbst enthält Änderungsmethode könnte es immer noch möglich sein, den Inhalt des Objekts zu modifizieren, obwohl die Änderung der Objektreferenz verhindert wurde.

class Program 
{ 
    static void Main(string[] args) 
    { 
     Person myPerson = new Person("Wenda"); 
     System.Console.WriteLine("myPerson is " + myPerson.name);  // Prints "myPerson is Wenda" 

     if (myPerson == Person.Waldo) 
      System.Console.WriteLine("Found Waldo (first attempt)"); // doesn't happen 
     else 
      System.Console.WriteLine("Still trying to find Waldo..."); // Prints "Still trying to find Waldo..." 

     myPerson.name = "Bozo"; 
     System.Console.WriteLine("myPerson is now " + myPerson.name); // Prints "myPerson is now Bozo" 

     myPerson = Person.Waldo; 

     if (myPerson == Person.Waldo) 
      System.Console.WriteLine("Found Waldo (second attempt)"); // Prints "Found Waldo (second attempt)" 

     System.Console.WriteLine("myPerson is " + myPerson.name);  // Prints "myPerson is Waldo" 
     System.Console.WriteLine("Now changing to The Joker...");  // Prints "Now changing to The Joker" 
     try 
     { 
      myPerson.name = "The Joker";        // throws ImmutablePersonModificationAttemptException 
     } 
     catch (ImmutablePersonModificationAttemptException) 
     { 
      System.Console.WriteLine("Failed to change");    // Prints "Failed to change" 
     } 
     System.Console.WriteLine("myPerson is now " + myPerson.name); // Prints "myPerson is now Waldo" 

     Thread.Sleep(int.MaxValue); // keep the console alive long enough for me to see the result. 
    } 
} 
public class Person 
{ 
    public static readonly ImmutablePerson Waldo = new ImmutablePerson("Waldo"); 
    public virtual string name { get; set; } 
    public Person() // empty base constructor required by ImmutablePerson(string) constructor 
    { } 
    public Person(string name) 
    { 
     this.name = name; 
    } 
} 
public class ImmutablePersonModificationAttemptException : Exception 
{ } 
public class ImmutablePerson : Person 
{ 
    private bool allowMutation; 
    protected string _name; 
    public override string name 
    { 
     get 
     { 
      return _name; 
     } 
     set 
     { 
      if (allowMutation) 
       _name = value; 
      else 
       throw new ImmutablePersonModificationAttemptException(); 
     } 
    } 
    public ImmutablePerson(string name) 
     : base() 
    { 
     allowMutation = true; 
     this.name = name; 
     allowMutation = false; 
    } 
} 
4

Machen Sie eine private Klasse innerhalb der Person, die die Person erbt, ImmutablePerson : Person.

Machen Sie alle Property Setter gesperrt: überschreiben Sie sie zum Beispiel mit NotImplementedException.

Ihre statische Person Initialisierung wird dies: public static readonly Person Waldo = new ImmutablePerson("Waldo");

Und statischer Konstruktor kann auch entfernt werden.

+0

+1, weil ich das gleiche sagen wollte. –

+0

Leider funktioniert das nicht. Sie können eine private Immobilie nicht öffentlich zugänglich machen und Waldo somit nicht öffentlich zugänglich machen. –

1

Vielleicht könnten Sie die folgende Hierarchie haben:

class Person 
{ 
    protected string _name; 
    public virtual string Name{ 
     get{ 
      return _name; 
     } 
    } 
} 

class EditablePerson:Person 
{ 
    public new string Name{ 
     get{ 
      return _name; 
     } 
     set{ 
      _name=value; 
     } 
    } 
    public Person AsPerson() 
    { 
     //either return this (and simply constrain by interface) 
     //or create an immutable copy 
    } 
} 
+0

Das hat nicht ganz funktioniert - die Get & Set-Accessoren müssen in der Basisklasse definiert werden, damit die "normale" Instanz änderbar ist. Sobald der Set-Accessor in der Basisklasse veröffentlicht wurde, können Sie den Zugriffsmodifizierer "private" oder "public" des abgeleiteten Accessors nicht mehr ändern. –

0

Meiner Meinung nach die beste immer eine neue Instanz auf Waldo zurückzukehren. Auf diese Weise wird der ursprüngliche Waldo nie verändert werden. Dies verhindert jedoch, dass Sie die Vergleichsgleichheit für Vergleichszwecke verwenden. Daher müssen Sie Equals und GetHashCode überschreiben.