2009-04-02 4 views
5

Я создал класс со свойствами, которые имеют значения по умолчанию. В какой-то момент времени жизни объекта я хотел бы «сбросить» свойства объекта до того, что было, когда объект был создан. Например, предположим, что это был класс:Как повторно инициализировать или сбросить свойства класса?

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

    public Truck() { } 

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

Тогда в какой-то момент, после того, как Name и Tires свойства были изменены, метод ResetTruck() можно назвать и свойства будут сбросить обратно «Super Truck» и 4 соответственно.

Каков наилучший способ вернуть свойства к исходным жестко заданным по умолчанию значениям?

ответ

9

Вы можете инициализировать метод, а не встраивать его в декларацию. Тогда есть конструктор и метод сброса вызова метода инициализации:

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

    public Truck() { 
     Init(); 
    } 

    public void ResetTruck() { 
     Init(); 
    } 

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

Другой способ не иметь метод сброса на всех. Просто создайте новый экземпляр.

+1

В какой-то степени это на самом деле то, что я делал, но мне было интересно, есть ли лучший способ. –

0

Возможно, вам нужно будет сохранить значения в частных полях, чтобы их можно было восстановить позже. Возможно, что-то вроде этого:

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

Если вы взяли этот подход, то я бы объявить значения по умолчанию в качестве статического сопзЬ. Ведь вы используете их как константы. – AaronLS

+0

Правда, я изменю это –

2

Если вы сделали инициализацию в методе Reset вы можете быть хорошо идти:

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

    public Truck() { 
    ResetTruck(); 
    } 

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

Если создание объекта не очень дорого (и сброс не по какой-то причине). Я не вижу причин для внедрения специального метода сброса. Почему бы вам просто не создать новый экземпляр с состоянием по умолчанию для использования.

Какова цель повторного использования экземпляра?

+0

Поскольку сам объект является тем, что будет выполнять сброс. Это не зависит от кода, который создает объект для инициирования сброса. –

+4

@ Дилан: Мне кажется, что класс может слишком много делать. Взгляните на принцип единой ответственности за дополнительную информацию. –

0

Вы в основном ищете State Design Pattern

+0

Извините, но НЕТ! Это не требует накладных расходов государственного образца, когда есть более чистые решения, хотя вы можете использовать его, если вы чувствуете себя мазохистом. :) – arviman

+0

Уточните ... –

+0

То, что я получаю, это то, что вы * можете * использовать шаблон состояния здесь для достижения этой функциональности, но это не имеет значения. Я бы порекомендовал вам прочитать образец, так как кажется, что он перепутался с «Государственными машинами». Шаблон состояния позволяет изменять поведение атрибута контекста на основе состояния. Здесь все, что вы делаете, - это возврат к исходному состоянию. Никаких изменений в поведении не требуется. – arviman

3

Отражение является вашим другом. Вы можете создать вспомогательный метод для использования Activator.CreateInstance() для установки значения по умолчанию типов значений и «null» для ссылочных типов, но зачем беспокоиться при установке значения null в SetValue PropertyInfo будет делать то же самое.

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. 

Чтобы продлить это для вашей цели, имеют частные члены:

//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"}; 

Устанавливаются значения в конструкторе:

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

Сброс их позже:

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

Я предпочитаю использовать 'FormatterServices.GetUninitializedObject()' over 'Activator.CreateInstance()'. Конструкторы пропускаются, все поля гарантированно имеют значение null/default. –

2

Сосредоточение разделения c oncerns (как Брайан упомянул в комментариях), другой альтернативы можно было бы добавить TruckProperties тип (можно даже добавить свои значения по умолчанию в конструктор):

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

Внутри вашего Truck класса, все вы могли бы сделать это управлять экземпляр типа TruckProperties, и пусть он выполнит сброс.

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

Это, конечно, может быть много (нежелательных) накладных расходов для такого простого класса, но в большей/более сложного проекта может быть выгодным.

Это то, что касается «лучших» практик ... много раз, нет серебряной пули, но только рекомендации, которые вы должны предпринять со скептицизмом и своим лучшим суждением относительно того, что относится к вам в конкретном случае.

0

Если вы хотите получить конкретное прошлое «состояние» вашего объекта, вы можете создать конкретную точку сохранения для возврата каждый раз, когда захотите. Это также позволит вам создать резервное копирование для создаваемого экземпляра everey. Если у вас есть много свойств, которые постоянно меняются, это может быть вашим решением.

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

Я решил аналогичную проблему с отражением. Вы можете использовать source.GetType().GetProperties(), чтобы получить список всех свойств, принадлежащих объекту.

Хотя это не всегда является полным решением. Если ваш объект реализует несколько интерфейсов, вы также получите все эти свойства с помощью вашего рефлекторного вызова.

Итак, я написал эту простую функцию, которая дает нам больший контроль над тем, какие свойства мы интересуемся сбросом.

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

И это использование:

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