简体   繁体   中英

How to get default compile-time value of Auto-Implemented property C# 6.0 after it changed?

Shortly, The new C# 6.0 Auto-Implemented Property allows us to make this

    public static bool IsSoundEffects { get; set; } = true;   // C# 6.0 allows this

Now in somewhere, I changed the property IsSoundEffects = false , So accessing it will be false.

hmm, So how to get the actual real default compile-time auto-implemented property value.

Something Like:
Type.GetPropertyDefaultValue(IsSoundEffects); // A real compile-time one = true

OR

default(IsSoundEffects)   // idk, something like that

Why I need that?

because I filling the properties from the database. and restore it if user need to restore the default values. for example settings.

Looks strange? I searched enough but all examples about the auto-implemented feature did not restore the default value.

Edited

The best approaches provided by

xiangbin.pang answer for reflection way [Short-One]

Christopher answers for constants as default values.

  1. For instance property, just new an instance then get the default property value is the easiest way.
  2. For static property, the default value can be preserved in the static constructor.
    public static class MyClass
    {
        public static int MyProp1 { get; set; } = 100;
        public static bool MyProp2 { get; set; } = false;

        private static Dictionary<string, object> defaultValues;

        static MyClass()
        {
            defaultValues = new Dictionary<string, object>();

            foreach(var prop in typeof(MyClass).GetProperties(BindingFlags.Static| BindingFlags.Public | BindingFlags.NonPublic))
            {
                defaultValues[prop.Name] = prop.GetValue(null);
            }
        }

        public static (T,bool) GetDefault<T>(string propName)
        {
            if(defaultValues.TryGetValue(propName, out object value))
            {
                return ((T)(value), true);
            }
            return (default, false);
        }
    }

    //test codes
    static void Main(string[] args)
    {

        MyClass.MyProp1 = 1000;
        MyClass.MyProp2 = true;

        var defaultValueOrProp1 = MyClass.GetDefault<int>("MyProp1");
        if(defaultValueOrProp1.Item2)
        {
            Console.WriteLine(defaultValueOrProp1.Item1);//100
        }

        var defaultValueOrProp2 = MyClass.GetDefault<bool>("MyProp2");
        if (defaultValueOrProp2.Item2)
        {
            Console.WriteLine(defaultValueOrProp2.Item1);//false
        }
    }



Following Line added by question author:

For setting property with default value

private static void ResetPropertyValue(string PropertyName)
{ 
    typeof(Options).GetProperty(PropertyName).SetValue(null, 
    defaultValues[PropertyName]);
}

Properties are little more then Syntax sugar for get/set function pairs. And what you got there is little more then a basic, bog-standart assignment/function call around when the constructor runs. As all literals and constatns, it should no longer exist at runtime.

The naive way would be to have a constant like IsSoundEffectsDefaultValue . And I do think that is sufficient in most cases.

There is a off-chance that a old idea of mine might apply to your broader problem. I need to search for the code however.

Edit:

I could not find my old code, unfortunately. I can re-create it however. The basic idea is to have multiple "layers" of values, with one value hiding (but not overwriting) the other.

public class defaultAble<T>{
    readonly T defaultValue;

    //constructor
    public defaultAble(T defaultValue){
        this.defaultValue = defaultValue;
        //First set the value
        RestoreDefault();
    }

    public RestoreDefault(){
        value = this.defaultValue;
    }

    public T value { get; set; }
}

Edit 2:

There might be a even better solution in WPF/MVVM circles. A lot of MVVM is writing properties with the same code - one that raises change notification by INotifyPropertyChanged. And a big issue with Properties is not writing the backing field by accident.

Some people figured out solutions like putting the actuall value and all the code into a something like a Dictionar<string, object> mostly automagically. I am not sure about the details, however. Also DependancyProperties might get close to it too.

One approach you could use would be based on Custom Attributes. You could define as Custom Attribute to hold the default value. For example,

public class DefaultValueAttribute:Attribute
{
    public object DefaultValue{get;set;}
    public DefaultValueAttribute(object defaultValue)=>DefaultValue = defaultValue;
}

You can now use the Attribute to store the default value as

public static class SomeClass
{
  [DefaultValueAttribute(true)]
  public static bool IsSoundEffects { get; set; } = true;
}

For retrieving the same, you could depend on reflection.

var defaultValue = typeof(SomeClass).GetProperty(nameof(SomeClass.IsSoundEffects), BindingFlags.Public | BindingFlags.Static)
                                .GetCustomAttribute<DefaultValueAttribute>().DefaultValue;

Making the reflection call a generic method to be used with other properties.

public T GetDefaultValue<T>(string propertyName)
{
    var result = typeof(SomeClass).GetProperty(nameof(SomeClass.IsSoundEffects), BindingFlags.Public | BindingFlags.Static)
                                .GetCustomAttribute<DefaultValueAttribute>().DefaultValue;

    return (T)Convert.ChangeType(result,typeof(T));
}

Usage

var defaultValue = GetDefaultValue<bool>(nameof(SomeClass.IsSoundEffects));

OP : I filling the properties from the database. and restore it if user need to restore the default values.

This post tries to answer based on based on above requirement which you mentioned in the question.(Not based on the question title.)

Option 1 - Using System.ComponentModel.DefaultValue

On of the standard solutions for setting default value for properties, is using DefaultValue attribute of the .NET Framework. You can see this pattern in a lot of classes in .NET Framework. For example:

[System.ComponentModel.DefaultValue(true)]
public static bool MyProperty{ get; set; } = true; 

Then .NET Framework gives you the reset functionality by PropertyDescriptor.ResetValue method:

System.ComponentModel.TypeDescriptor.GetProperties(this)["MyProperty"].ResetValue(this);

Option 2 - Create a static Default instance

You can have a static default instance of the class and use it for resetting properties.

If you want to have the default instance as private you can keep it as a single instance field.

If you want the default instance as public you need to create a property and return a new instance in the getter.

public class MyClass
{
    private static readonly MyClass Default = new MyClass();
    //OR
    //public static MyClass Default { get { return new MyClass(); } }

    public bool MyProperty{ get; set; } = true;
    public void Reset()
    {
        this.MyProperty = Default.MyProperty;
    }
}

Use PropertyInfo.GetConstantValue Method .

Consider your class

class YourClass
{
    public static bool IsSoundEffects { get; set; } = true;   // C# 6.0 allows this
}

and consider a "getter" class you create

class DefaultGetter
{
    T GetDefault<TClass, TProp>(string propertyName) where T : new()
    {        
        if (!defaults.TryGetValue(typeof(T), out var defaultValues)
            if (!defaultValues.TryGetValue(propertyName, out var value))
            {
                 // reflect on prototype getting all the PropertyInfo
                 // and store them in the dictionary of dictionaries
                 // then assign to value...
                 defaults[typeof(TClass)] = defaultValues = typeof(TClass).GetProperties().Select(x => new { x.Name, Value = x.GetConstantValue() }).ToDictionary(x => x.Name);
            }

            value = defaultValues[propertyName]
        }

        return (TProp)value;
    }

    IDictionary<Type, IDictionary<string, object> defaults = new ... // elided for brevity
}

You would use it like this:

var defaultIsSoundEffects = defaultGetter.GetDefault<YourClass, bool>("IsSoundEffects");

You can use following approaches

  1. using constants which is good if no need to change variables after initializations.
  2. use static readonly if only variable can changed in cosntructor.
  3. use dictionary with

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