2009-04-02 11 views
5

Ich habe eine Klasse mit Eigenschaften erstellt, die Standardwerte haben. Irgendwann während der Lebensdauer des Objekts möchte ich die Eigenschaften des Objekts zurück auf das zurücksetzen, was sie waren, als das Objekt instanziiert wurde. Zum Beispiel lassen Sie sich sagen, dass dies die Klasse ist:Wie initialisiere oder setze ich die Eigenschaften einer Klasse neu?

public class Truck { 
    public string Name = "Super Truck"; 
    public int Tires = 4; 

    public Truck() { } 

    public void ResetTruck() { 
     // Do something here to "reset" the object 
    } 
} 

Dann irgendwann, nachdem die Name und Tires Eigenschaften verändert wurden, die ResetTruck() Methode genannt werden könnte und die Eigenschaften zurückgesetzt werden würden zurück zu „Super Truck“ und 4 jeweils.

Was ist der beste Weg, um die Eigenschaften auf ihre ursprünglichen hartcodierten Standardwerte zurückzusetzen?

Antwort

9

Sie können die Initialisierung in einer Methode statt inlining mit der Deklaration haben. Dann haben Sie den Konstruktor und das Verfahren die Initialisierungsmethode zurückgesetzt rufen:

public class Truck { 
    public string Name; 
    public int Tires; 

    public Truck() { 
     Init(); 
    } 

    public void ResetTruck() { 
     Init(); 
    } 

    private void Init() { 
     Name = "Super Truck"; 
     Tires = 4; 
    } 
} 

Ein anderer Weg ist kein Reset-Verfahren überhaupt zu haben. Erstellen Sie einfach eine neue Instanz.

+1

Bis zu einem gewissen Grad ist das tatsächlich, was ich tat, aber ich fragte mich, ob es einen besseren Weg gab. –

0

Sie müssen die Werte wahrscheinlich in privaten Feldern speichern, damit sie später wiederhergestellt werden können. Vielleicht etwas in der Art:

public class Truck 
{ 
    private static const string defaultName = "Super Truck"; 
    private static const int defaultTires = 4; 

    // Use properties for public members (not public fields) 
    public string Name { get; set; } 
    public int Tires { get; set; } 

    public Truck() 
    { 
     Name = defaultName; 
     Tires = defaultTires; 
    } 

    public void ResetTruck() 
    { 
     Name = defaultName; 
     Tires = defaultTires; 
    } 

} 
+2

Wenn Sie diesen Ansatz nehmen, dann würde ich die Standardwerte als static const deklariert. Sie verwenden sie schließlich wie Konstanten. – AaronLS

+0

Wahr, ich werde das ändern –

2

Wenn Sie Ihre Initialisierung in einem Reset-Methode, so können Sie gut zu gehen:

public class Truck { 
    public string Name; 
    public int Tires; 

    public Truck() { 
    ResetTruck(); 
    } 

    public void ResetTruck() { 
     Name = "Super Truck"; 
     Tires = 4; 
    } 
} 
3

Es sei denn, das Objekt zu schaffen wirklich teuer ist (und Reset ist nicht aus irgendeinem Grund). Ich sehe keinen Grund, eine spezielle Reset-Methode zu implementieren. Warum erstellen Sie nicht einfach eine neue Instanz mit einem verwendbaren Standardzustand?

Welchen Zweck hat die Wiederverwendung der Instanz?

+0

Weil das Objekt selbst das Zurücksetzen tut. Es ist nicht der Code, der das Objekt veranlasst, das Zurücksetzen zu initiieren. –

+4

@Dylan: Es klingt für mich, als würde die Klasse zu viel tun. Werfen Sie einen Blick auf das Prinzip der einheitlichen Verantwortung für weitere Informationen. –

0

Sie suchen im Wesentlichen für die State Design Muster

+0

Entschuldigung, aber NEIN! Dies erfordert nicht den Overhead eines Zustandsmusters, wenn es sauberere Lösungen gibt, obwohl Sie es verwenden könnten, wenn Sie sich wie ein Masochist fühlen. :) – arviman

+0

Do elaborate ... –

+0

Was ich bekomme ist, dass Sie * hier * das Zustandsmuster verwenden können, um diese Funktionalität zu erreichen, aber es ist nicht relevant. Ich würde dir empfehlen, das Muster durchzulesen, da es so aussieht, als hättest du es mit "Staatsmaschinen" durcheinander gebracht. Mit dem Statusmuster können Sie das Verhalten eines Attributs eines Kontexts basierend auf dem Status ändern. Hier wird lediglich in einen Ausgangszustand zurückversetzt. Es ist keine Änderung im Verhalten erforderlich. – arviman

3

Reflexion dein Freund. Sie könnten eine Hilfsmethode erstellen, um Activator.CreateInstance() zu verwenden, um den Standardwert von Werttypen und 'null' für Referenztypen festzulegen, aber warum sollten Sie beim Festlegen von null auf SetValue von PropertyInfo das Gleiche tun?

Type type = this.GetType(); 
    PropertyInfo[] properties = type.GetProperties(); 
    for (int i = 0; i < properties.Length; ++i) 
     properties[i].SetValue(this, null); //trick that actually defaults value types too. 

Um dies für Ihre Zwecke zu erweitern, haben private Mitglieder:

//key - property name, value - what you want to assign 
Dictionary<string, object> _propertyValues= new Dictionary<string, object>(); 
List<string> _ignorePropertiesToReset = new List<string>(){"foo", "bar"}; 

die Werte in Ihrem Konstruktor Set:

public Truck() { 
    PropertyInfo[] properties = type.GetProperties(); 

    //exclude properties you don't want to reset, put the rest in the dictionary 
    for (int i = 0; i < properties.Length; ++i){ 
     if (!_ignorePropertiesToReset.Contains(properties[i].Name)) 
      _propertyValues.Add(properties[i].Name, properties[i].GetValue(this)); 
    } 
} 

sie später Reset:

public void Reset() { 
    PropertyInfo[] properties = type.GetProperties(); 
    for (int i = 0; i < properties.Length; ++i){ 
     //if dictionary has property name, use it to set the property 
     properties[i].SetValue(this, _propertyValues.ContainsKey(properties[i].Name) ? _propertyValues[properties[i].Name] : null);  
    } 
} 
+0

Ich bevorzuge 'FormatterServices.GetUninitializedObject()' über 'Activator.CreateInstance()'. Konstruktoren werden übersprungen, alle Felder sind garantiert null/default. –

2

Fokussierung der Trennung von c oncerns (wie Brian in den Kommentaren erwähnt), wäre eine weitere Alternative sein, einen TruckProperties Typ hinzuzufügen (Sie sogar Ihre Standardwerte an den Konstruktor hinzufügen könnte):

public class TruckProperties 
{ 
    public string Name 
    { 
     get; 
     set; 
    } 

    public int Tires 
    { 
     get; 
     set; 
    } 

    public TruckProperties() 
    { 
     this.Name = "Super Truck"; 
     this.Tires = 4; 
    } 

    public TruckProperties(string name, int tires) 
    { 
     this.Name = name; 
     this.Tires = tires; 
    } 
} 

In Ihrem Truck Klasse, alles, was Sie tun würde, ist verwalten eine Instanz des Typs TruckProperties, und lassen Sie es tun, zurückgesetzt.

public class Truck 
{ 
    private TruckProperties properties = new TruckProperties(); 

    public Truck() 
    { 
    } 

    public string Name 
    { 
     get 
     { 
      return this.properties.Name; 
     } 
     set 
     { 
      this.properties.Name = value; 
     } 
    } 

    public int Tires 
    { 
     get 
     { 
      return this.properties.Tires; 
     } 
     set 
     { 
      this.properties.Tires = value; 
     }   
    } 

    public void ResetTruck() 
    { 
     this.properties = new TruckProperties(); 
    } 
} 

Dies kann sicherlich eine Menge (unerwünschten) Aufwand für eine solche einfache Klasse, aber in einem größeren/komplexeren Projekt könnte es von Vorteil sein.

Das ist die Sache mit "besten" Praktiken ... viele Male gibt es keine Wunderwaffe, sondern nur Empfehlungen, die Sie mit Skepsis und Ihrem besten Urteil darüber, was für Sie in einem bestimmten Fall gilt, nehmen müssen.

0

Wenn Sie einen bestimmten "Status" Ihres Objekts haben möchten, können Sie einen bestimmten Sicherungspunkt erstellen, der jedes Mal zurückgegeben wird, wenn Sie möchten. Auf diese Weise können Sie auch für jede von Ihnen erstellte Instanz einen anderen Sicherungsstatus festlegen. Wenn Ihre Klasse viele Eigenschaften hat, die sich ständig ändern, könnte dies Ihre Lösung sein.

public class Truck 
{ 
    private string _Name = "Super truck"; 
    private int _Tires = 4; 

    public string Name 
    { 
     get { return _Name; } 
     set { _Name = value; } 
    } 
    public int Tires 
    { 
     get { return _Tires; } 
     set { _Tires = value; } 
    } 

    private Truck SavePoint; 

    public static Truck CreateWithSavePoint(string Name, int Tires) 
    { 
     Truck obj = new Truck(); 
     obj.Name = Name; 
     obj.Tires = Tires; 
     obj.Save(); 
     return obj; 
    } 

    public Truck() { } 

    public void Save() 
    { 
     SavePoint = (Truck)this.MemberwiseClone(); 
    } 

    public void ResetTruck() 
    { 
     Type type = this.GetType(); 
     PropertyInfo[] properties = type.GetProperties(); 
     for (int i = 0; i < properties.Count(); ++i) 
      properties[i].SetValue(this, properties[i].GetValue(SavePoint)); 
    } 
} 
0

Ich löste ein ähnliches Problem mit der Reflexion. Sie können source.GetType().GetProperties() verwenden, um eine Liste aller Eigenschaften zu erhalten, die zu dem Objekt gehören.

Dies ist jedoch nicht immer eine vollständige Lösung. Wenn Ihr Objekt mehrere Schnittstellen implementiert, erhalten Sie alle diese Eigenschaften auch mit Ihrem Reflektionsaufruf.

Also habe ich diese einfache Funktion geschrieben, die uns mehr Kontrolle darüber gibt, welche Eigenschaften wir zurücksetzen möchten.

public static void ClearProperties(object source, List<Type> InterfaceList = null, Type SearchType = null) 
    { 


     // Set Interfaces[] array size accordingly. (Will be size of our passed InterfaceList, or 1 if InterfaceList is not passed.) 
     Type[] Interfaces = new Type[InterfaceList == null ? 1 : InterfaceList.Count]; 

     // If our InterfaceList was not set, get all public properties. 
     if (InterfaceList == null) 
      Interfaces[0] = source.GetType(); 
     else // Otherwise, get only the public properties from our passed InterfaceList 
      for (int i = 0; i < InterfaceList.Count; i++) 
       Interfaces[i] = source.GetType().GetInterface(InterfaceList[i].Name); 


     IEnumerable<PropertyInfo> propertyList = Enumerable.Empty<PropertyInfo>(); 
     foreach (Type face in Interfaces) 
     { 
      if (face != null) 
      { 
       // If our SearchType is null, just get all properties that are not already empty 
       if (SearchType == null) 
        propertyList = face.GetProperties().Where(prop => prop != null); 
       else // Otherwise, get all properties that match our SearchType 
        propertyList = face.GetProperties().Where(prop => prop.PropertyType == SearchType); 

       // Reset each property 
       foreach (var property in propertyList) 
       { 
        if (property.CanRead && property.CanWrite) 
         property.SetValue(source, null, new object[] { }); 
       } 
      } 
      else 
      { 
       // Throw an error or a warning, depends how strict you want to be I guess. 
       Debug.Log("Warning: Passed interface does not belong to object."); 
       //throw new Exception("Warning: Passed interface does not belong to object."); 
      } 
     } 
    } 

Und es ist Gebrauch:

// Clears all properties in object 
ClearProperties(Obj); 
// Clears all properties in object from MyInterface1 & MyInterface2 
ClearProperties(Obj, new List<Type>(){ typeof(MyInterface1), typeof(MyInterface2)}); 
// Clears all integer properties in object from MyInterface1 & MyInterface2 
ClearProperties(Obj, new List<Type>(){ typeof(MyInterface1), typeof(MyInterface2)}, typeof(int)); 
// Clears all integer properties in object 
ClearProperties(Obj,null,typeof(int));