Can you detect if a C# field has been assigned a d

2019-03-27 10:28发布

Say you have a class declaration, e.g.:


class MyClass
{
  int myInt=7;
  int myOtherInt;
}

Now, is there a way in generic code, using reflection (or any other means, for that matter), that I can deduce that myInt has a default value assigned, whereas myOtherInt does not? Note the difference between being initialised with an explicit default value, and being left to it's implicit default value (myOtherInt will be initialised to 0, by default).

From my own research it looks like there is no way to do this - but I thought I'd ask here before giving up.

[Edit]

Even with nullable and reference types I want to distingush between those that have been left as null, and those that have been explicitly initialised to null. This is so that I can say that fields with an initialiser are "optional" and other fields are "mandatory". At the moment I'm having to do this using attributes - which niggles me with their redundancy of information in this case.

12条回答
啃猪蹄的小仙女
2楼-- · 2019-03-27 10:57

May be this is not the simplest solution...

You can use de DefaultValue attribute to set the value like:

Import System.ComponentModel and System.Reflection

private int myNumber = 3;
[System.ComponentModel.DefaultValue(3)]
public int MyNumber
{
    get
    {
        return myNumber;
    }
    set
    {
        myNumber = value;
    }
}

And then recover the default value with reflection:

PropertyInfo prop = this.GetType().GetProperty("MyNumber");
MessageBox.Show(((DefaultValueAttribute)(prop.GetCustomAttributes(typeof(DefaultValueAttribute), true).GetValue(0))).Value.ToString());
查看更多
贼婆χ
3楼-- · 2019-03-27 10:57

This approach uses the property get/set process:

    class myClass
    {
       #region Property: MyInt
       private int _myIntDefault = 7;
       private bool _myIntChanged = false;
       private int _myInt;
       private int MyInt
       {
          get
          {
             if (_myIntChanged)
             {
                return _myInt;
             }
             else
             {
                return _myIntDefault;
             }
          }
          set
          {
             _myInt = value;
             _myIntChanged = true;
          }
       }

       private bool MyIntIsDefault
       {
          get
          {
             if (_myIntChanged)
             {
                return (_myInt == _myIntDefault);
             }
             else
             {
                return true;
             }
          }
       }
       #endregion
    }

That's alot of code for one field - hello snippets!

查看更多
Luminary・发光体
4楼-- · 2019-03-27 10:57

If what you want is this, then check out the code at the bottom.
It's written in Oxygene[1], hope that's not a problem.

[1]or Delphi Prism how it's called now


var inst1 := new Sample();
var inst2 := new Sample(X := 2);

var test1 := new DefaultValueInspector<Sample>(true);
var test2 := new DefaultValueInspector<Sample>(inst2, true);

var d := test1.DefaultValueByName["X"];

var inst1HasDefault := test1.HasDefaultValue(inst1, "X");
var inst2HasDefault := test1.HasDefaultValue(inst2, "X");

Console.WriteLine("Value: {0}; inst1HasDefault: {1}; inst2HasDefault {2}",
                  d, inst1HasDefault, inst2HasDefault);

d := test2.DefaultValueByName["X"];

inst1HasDefault := test2.HasDefaultValue(inst1, "X");
inst2HasDefault := test2.HasDefaultValue(inst2, "X");

Console.WriteLine("Value: {0}; inst1HasDefault: {1}; inst2HasDefault {2}",
                  d, inst1HasDefault, inst2HasDefault);

Output:

Value: 1; inst1HasDefault: True; inst2HasDefault False
Value: 2; inst1HasDefault: False; inst2HasDefault True


uses 
    System.Collections.Generic, 
    System.Reflection;

type
    DefaultValueInspector<T> = public class
    private
        method get_DefaultValueByName(memberName : String): Object;
        method get_DefaultValueByMember(memberInfo : MemberInfo) : Object;
   protected
        class method GetMemberErrorMessage(memberName : String) : String;
        method GetMember(memberName : String) : MemberInfo;

        property MembersByName : Dictionary<String, MemberInfo> 
            := new Dictionary<String, MemberInfo>(); readonly;

        property GettersByMember : Dictionary<MemberInfo, Converter<T, Object>> 
            := new Dictionary<MemberInfo, Converter<T, Object>>(); readonly;

        property DefaultValuesByMember : Dictionary<MemberInfo, Object> 
            := new Dictionary<MemberInfo, Object>(); readonly;
    public
        property UseHiddenMembers : Boolean; readonly;

        property DefaultValueByName[memberName : String] : Object
            read get_DefaultValueByName;
        property DefaultValueByMember[memberInfo : MemberInfo] : Object
            read get_DefaultValueByMember;

        method GetGetMethod(memberName : String) : Converter<T, Object>;
        method GetGetMethod(memberInfo : MemberInfo) : Converter<T, Object>;

        method HasDefaultValue(instance : T; memberName : String) : Boolean;
        method HasDefaultValue(instance : T; memberInfo : MemberInfo) : Boolean;

        constructor(useHiddenMembers : Boolean);
        constructor(defaultInstance : T; useHiddenMembers : Boolean);    
  end;

implementation

constructor DefaultValueInspector<T>(useHiddenMembers : Boolean);
begin
    var ctorInfo := typeOf(T).GetConstructor([]);
    constructor(ctorInfo.Invoke([]) as T, useHiddenMembers);
end;

constructor DefaultValueInspector<T>(defaultInstance : T; useHiddenMembers : Boolean);
begin
    var bf := iif(useHiddenMembers, 
                  BindingFlags.NonPublic)
              or BindingFlags.Public
              or BindingFlags.Instance;

    for mi in typeOf(T).GetMembers(bf) do
        case mi.MemberType of
            MemberTypes.Field :
            with matching fi := FieldInfo(mi) do
            begin
                MembersByName.Add(fi.Name, fi);
                GettersByMember.Add(mi, obj -> fi.GetValue(obj));
            end;
            MemberTypes.Property :
            with matching pi := PropertyInfo(mi) do
                if pi.GetIndexParameters().Length = 0 then
                begin
                   MembersByName.Add(pi.Name, pi);
                   GettersByMember.Add(mi, obj -> pi.GetValue(obj, nil));
                end;
        end;

    for g in GettersByMember do
        with val := g.Value(DefaultInstance) do
            if assigned(val) then 
                DefaultValuesByMember.Add(g.Key, val);
end;

class method DefaultValueInspector<T>.GetMemberErrorMessage(memberName : String) : String;
begin
    exit "The member '" + memberName + "' does not exist in type " + typeOf(T).FullName 
         + " or it has indexers."
end;

method DefaultValueInspector<T>.get_DefaultValueByName(memberName : String): Object;
begin
    var mi := GetMember(memberName);
    DefaultValuesByMember.TryGetValue(mi, out result);
end;

method DefaultValueInspector<T>.get_DefaultValueByMember(memberInfo : MemberInfo) : Object;
begin
    if not DefaultValuesByMember.TryGetValue(memberInfo, out result) then
        raise new ArgumentException(GetMemberErrorMessage(memberInfo.Name),
                                    "memberName"); 
end;

method DefaultValueInspector<T>.GetGetMethod(memberName : String) : Converter<T, Object>;
begin
    var mi := GetMember(memberName);
    exit GetGetMethod(mi);
end;

method DefaultValueInspector<T>.GetGetMethod(memberInfo : MemberInfo) : Converter<T, Object>;
begin
    if not GettersByMember.TryGetValue(memberInfo, out result) then
        raise new ArgumentException(GetMemberErrorMessage(memberInfo.Name),
                                    "memberName"); 
end;

method DefaultValueInspector<T>.GetMember(memberName : String) : MemberInfo;
begin
    if not MembersByName.TryGetValue(memberName, out result) then
        raise new ArgumentException(GetMemberErrorMessage(memberName),
                                    "memberName"); 
end;

method DefaultValueInspector<T>.HasDefaultValue(instance : T; memberName : String) : Boolean;
begin
    var getter := GetGetMethod(memberName);
    var instanceValue := getter(instance);
    exit Equals(DefaultValueByName[memberName], instanceValue);
end;

method DefaultValueInspector<T>.HasDefaultValue(instance : T; memberInfo : MemberInfo) : Boolean;
begin
    var getter := GetGetMethod(memberInfo);
    var instanceValue := getter(instance);
    exit Equals(DefaultValueByMember[memberInfo], instanceValue);
end;
查看更多
仙女界的扛把子
5楼-- · 2019-03-27 10:58

A default value is a value like any other. There is no way to differentiate between these two cases:

int explicitly = 0;
int implicitly;

In both cases, you give them the value 0, one way just saves you typing. There is no magic "default uninitialized value" - they are both zero. They work out to be exactly the same. However, the fact that you are even contemplating this indicates that you are seriously off the track of good ideas. What are you doing? What is your specific need? You are asking the wrong question ;)

查看更多
贪生不怕死
6楼-- · 2019-03-27 11:00

Here's what I'd do if I wanted to build this as a general runtime feature. For scalar types, I'd create a default value attribute and use that to determine defaulticity.

Here's a partial solution to the task - I'm sure it could be better, but I just knocked it out:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Linq;
using System.Data;


namespace FieldAttribute
{
    [global::System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
    sealed class DefaultValueAttribute : Attribute
    {
        public DefaultValueAttribute(int i)
        {
            IntVal = i;
        }

        public DefaultValueAttribute(bool b)
        {
            BoolVal = b;
        }

        public int IntVal { get; set; }
        public bool BoolVal { get; set; }

        private static FieldInfo[] GetAttributedFields(object o, string matchName)
        {
            Type t = o.GetType();
            FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            return fields.Where(fi => ((matchName != null && fi.Name == matchName) || matchName == null) &&
                            (fi.GetCustomAttributes(false).Where(attr => attr is DefaultValueAttribute)).Count() > 0).ToArray();
        }

        public static void SetDefaultFieldValues(object o)
        {
            FieldInfo[] fields = GetAttributedFields(o, null);
            foreach (FieldInfo fi in fields)
            {
                IEnumerable<object> attrs = fi.GetCustomAttributes(false).Where(attr => attr is DefaultValueAttribute);
                foreach (Attribute attr in attrs)
                {
                    DefaultValueAttribute def = attr as DefaultValueAttribute;
                    Type fieldType = fi.FieldType;
                    if (fieldType == typeof(Boolean))
                    {
                        fi.SetValue(o, def.BoolVal);
                    }
                    if (fieldType == typeof(Int32))
                    {
                        fi.SetValue(o, def.IntVal);
                    }
                }
            }
        }

        public static bool HasDefaultValue(object o, string fieldName)
        {
            FieldInfo[] fields = GetAttributedFields(o, null);
            foreach (FieldInfo fi in fields)
            {
                IEnumerable<object> attrs = fi.GetCustomAttributes(false).Where(attr => attr is DefaultValueAttribute);
                foreach (Attribute attr in attrs)
                {
                    DefaultValueAttribute def = attr as DefaultValueAttribute;
                    Type fieldType = fi.FieldType;
                    if (fieldType == typeof(Boolean))
                    {
                        return (Boolean)fi.GetValue(o) == def.BoolVal;
                    }
                    if (fieldType == typeof(Int32))
                    {
                        return (Int32)fi.GetValue(o) == def.IntVal;
                    }
                }
            }
            return false;
        }
    }

    class Program
    {
        [DefaultValue(3)]
        int foo;

        [DefaultValue(true)]
        bool b;

        public Program()
        {
            DefaultValueAttribute.SetDefaultFieldValues(this);
            Console.WriteLine(b + " " + foo);
            Console.WriteLine("b has default value? " + DefaultValueAttribute.HasDefaultValue(this, "b"));
            foo = 2;
            Console.WriteLine("foo has default value? " + DefaultValueAttribute.HasDefaultValue(this, "foo"));
        }

        static void Main(string[] args)
        {
            Program p = new Program();
        }
    }
}
查看更多
聊天终结者
7楼-- · 2019-03-27 11:02

Does the following help:

bool isAssigned = (myOtherInt == default(int));
查看更多
登录 后发表回答