简体   繁体   中英

How do I reinitialize or reset the properties of a class?

I've created a class with properties that have default values. At some point in the object's lifetime, I'd like to "reset" the object's properties back to what they were when the object was instantiated. For example, let's say this was the class:

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

   public Truck() { }

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

Then at some point, after the Name and Tires properties have been changed, the ResetTruck() method could be called and the properties would be reset back to "Super Truck" and 4, respectively.

What's the best way to reset the properties back to their initial hard-coded defaults?

You can have the initialization in a method instead of inlining with the declaration. Then have the constructor and reset method call the initialization method:

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

   public Truck() {
      Init();
   }

   public void ResetTruck() {
      Init();
   }

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

Another way is not to have a reset method at all. Just create a new instance.

Reflection is your friend. You could create a helper method to use Activator.CreateInstance() to set the default value of Value types and 'null' for reference types, but why bother when setting null on a PropertyInfo's SetValue will do the same.

    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.

To extend this for your purpose, have private members:

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

Set the values in your constructor:

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

Reset them later:

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

Unless creating the object is really expensive (and Reset isn't for some reason). I see no reason to implement a special reset method. Why don't you just create a new instance with a usable default state.

What is the purpose of reusing the instance?

If you did your initialization in a Reset method you can be good to go:

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

   public Truck() {
    ResetTruck();
  }

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

Focusing of separation of concerns (like Brian mentioned in the comments), another alternative would be to add a TruckProperties type (you could even add your default values to its constructor):

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

Inside your Truck class, all you would do is manage an instance of the TruckProperties type, and let it do its reset.

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

This certainly may be a lot of (unwanted) overhead for such a simple class, but in a bigger/more complex project it could be advantageous.

That's the thing about "best" practices... a lot of times, there's no silver bullet, but only recommendations you must take with skepticism and your best judgement as to what applies to you in a particular case.

I solved a similar problem with reflection. You can use source.GetType().GetProperties() to get a list of all properties which belong to the object.

Although, this is not always a complete solution. If your object implements several interfaces, you will also get all those properties with your reflection call.

So I wrote this simple function which gives us more control of which properties we are interested in resetting.

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

And it's use:

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

If you want a specific past "state" of your object you can create a particular save point to return every time you want. This also let you have a diferent state to backup for everey instance that you create. If you class has many properties who are in constant change, this could be your solution.

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

If you aren't using a Code Generator or a Designer that would conflict, another option is to go through C#'s TypeDescriptor stuff, which is similar to Reflection, but meant to add more meta information to a class than Reflection could.

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

Note that ResetValue will also reset to a shadowed property if one exists. The priority of which option is selected is explained in the docs :

This method determines the value to reset the property to in the following order of precedence:

  1. There is a shadowed property for this property.
  2. There is a DefaultValueAttribute for this property.
  3. There is a "ResetMyProperty" method that you have implemented, where "MyProperty" is the name of the property you pass to it.

You'd probably need to save the values off in private fields, so that they can be restored later. Maybe something like this:

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

}

You're essentially looking for the State Design Pattern

You may represent an object state as a struct or record struct and then set the state to the default value in the Reset method like this:

public class Truck {
   record struct State(string Name, int Tires);   
   private static readonly State _defaultState = new("Super Truck", 4);

   private State _state = _defaultState;

   public string Name => _state.Name;
   public int Tires => _state.Tires;

   public Truck() {}

   public void ResetTruck() => _state = _defaultState;
}

It is probably the fastest way as well.

Also, a record struct will give you the trivial implementations of the ToString , Equals , GetHashCode .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM