【问题标题】:How do I reinitialize or reset the properties of a class?如何重新初始化或重置类的属性?
【发布时间】:2009-04-02 04:56:26
【问题描述】:

我创建了一个具有默认值属性的类。在对象生命周期的某个时刻,我想将对象的属性“重置”回实例化对象时的状态。例如,假设这是类:

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

   public Truck() { }

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

然后在某个时刻,NameTires 属性被更改后,ResetTruck() 方法可以被调用,属性将分别重置回“超级卡车”和 4。

将属性重置为其初始硬编码默认值的最佳方法是什么?

【问题讨论】:

    标签: c# class properties instantiation


    【解决方案1】:

    您可以在方法中进行初始化,而不是在声明中内联。然后让构造函数和reset方法调用初始化方法:

    public class Truck {
       public string Name;
       public int Tires;
    
       public Truck() {
          Init();
       }
    
       public void ResetTruck() {
          Init();
       }
    
       private void Init() {
          Name = "Super Truck";
          Tires = 4;
       }
    }
    

    另一种方法是根本没有重置方法。只需创建一个新实例。

    【讨论】:

    • 在某种程度上,这实际上是我正在做的,但我想知道是否有更好的方法。
    【解决方案2】:

    反思是你的朋友。您可以创建一个辅助方法来使用 Activator.CreateInstance() 来设置值类型的默认值和引用类型的“null”,但是为什么在 PropertyInfo 的 SetValue 上设置 null 时会这样做。

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

    【讨论】:

    • 我更喜欢使用FormatterServices.GetUninitializedObject() 而不是Activator.CreateInstance()。构造函数被跳过,所有字段都保证为空/默认。
    • 如果你有只读属性,你可以在设置值之前检查CanWrite是否为真SetValue
    • type.GetProperties() 只返回属性,不返回成员。而是在方法参数中使用带有标志属性的GetFields(BindingFlags.Instance | BindingFlags.NonPublic BindingFlags.Public)
    【解决方案3】:

    除非创建对象真的很昂贵(并且重置不是出于某种原因)。我认为没有理由实施特殊的重置方法。为什么不直接创建一个具有可用默认状态的新实例。

    重用实例的目的是什么?

    【讨论】:

    • 因为对象本身就是要进行重置的对象。这不取决于实例化对象的代码来启动重置。
    • @Dylan:在我看来,这门课可能做得太多了。查看单一职责原则了解更多信息。
    【解决方案4】:

    我用反射解决了类似的问题。您可以使用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));
    

    【讨论】:

      【解决方案5】:

      如果您在 Reset 方法中进行了初始化,您就可以开始了:

      public class Truck {
         public string Name;
         public int Tires;
      
         public Truck() {
          ResetTruck();
        }
      
         public void ResetTruck() {
            Name = "Super Truck";
            Tires = 4;
         }
      }
      

      【讨论】:

        【解决方案6】:

        关注关注点分离(就像在 cmets 中提到的 Brian),另一种选择是添加 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();
            }
        }
        

        对于这样一个简单的类来说,这当然可能是很多(不需要的)开销,但在更大/更复杂的项目中它可能是有利的。

        这就是“最佳”实践的意义……很多时候,没有灵丹妙药,只有建议您必须带着怀疑的态度和您对在特定情况下适用于您的最佳判断来采纳建议。

        【讨论】:

          【解决方案7】:

          您可能需要将值保存在私有字段中,以便以后可以恢复。也许是这样的:

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

          【讨论】:

          • 如果您采用这种方法,那么我会将默认值声明为静态常量。毕竟你是像常量一样使用它们。
          【解决方案8】:

          您实际上是在寻找State 设计模式

          【讨论】:

          • 对不起,不!当有更清洁的解决方案时,这不需要状态模式的开销,尽管如果你感觉自己像个受虐狂,你可以使用它。 :)
          • 我的意思是您可以使用此处的状态模式来实现此功能,但这并不相关。我建议您通读该模式,因为它似乎与“状态机”混淆了。状态模式允许您根据状态更改上下文属性的行为。在这里,您所做的只是恢复到初始状态。无需改变行为。
          【解决方案9】:

          如果您想要对象的特定过去“状态”,您可以创建一个特定的保存点以在每次需要时返回。这也让您可以为您创建的每个实例备份不同的状态。如果您的班级有许多不断变化的属性,这可能是您的解决方案。

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

          【讨论】:

            【解决方案10】:

            如果您没有使用会发生冲突的代码生成器或设计器,另一种选择是通过 C# 的 TypeDescriptor 东西,这类似于反射,但意味着向类添加比反射更多的元信息.

            using System.ComponentModel;
            
            public class Truck {
               // You can use the DefaultValue Attribute for simple primitive properites
               [DefaultValue("Super Truck")]
               public string Name { get; set; } = "Super Truck";
            
               // You can use a Reset[PropertyName]() method for more complex properties
               public int Tires { get; set; } = 4;
               public void ResetTires() => Tires = 4; 
               
            
               public Truck() { }
            
               public void ResetTruck() {
                  // Iterates through each property and tries to reset it
                  foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(GetType())) {
                    if (prop.CanResetValue(this))  prop.ResetValue(this);
                  }
               }
            }
            

            请注意,如果存在,ResetValue 也会重置为阴影属性。 the docs中解释了选择哪个选项的优先级:

            此方法按以下优先顺序确定将属性重置为的值:

            1. 此属性有一个阴影属性。
            2. 此属性有一个 DefaultValueAttribute。
            3. 您已经实现了一个“ResetMyProperty”方法,其中“MyProperty”是您传递给它的属性的名称。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-06-03
              • 2016-03-05
              • 2015-05-07
              • 1970-01-01
              • 1970-01-01
              • 2016-10-05
              • 1970-01-01
              相关资源
              最近更新 更多