【问题标题】:Can you detect if a C# field has been assigned a default value?你能检测到 C# 字段是否被赋予了默认值吗?
【发布时间】:2008-11-07 12:20:26
【问题描述】:

假设你有一个类声明,例如:


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

现在,在通用代码中有没有一种方法,使用反射(或任何其他方式,就此而言),我可以推断出 myInt 具有分配的默认值,而 myOtherInt 没有? 请注意使用显式默认值初始化和留给它的隐式默认值之间的区别(默认情况下,myOtherInt 将初始化为 0)。

根据我自己的研究,似乎没有办法做到这一点 - 但我想在放弃之前我会在这里问一下。

[编辑]

即使是可空类型和引用类型,我也想区分保留为空的类型和已显式初始化为空的类型。这样我就可以说带有初始化程序的字段是“可选的”,而其他字段是“强制性的”。目前,我不得不使用属性来做到这一点——在这种情况下,它们的信息冗余让我很恼火。

【问题讨论】:

  • 我不这么认为,没有自己使用反射跟踪 ctor,注意哪个字段被触及,哪个字段没有被触及。
  • 艾伦 - 这和 chakrit 的建议差不多。您有什么不想发布实际回复的原因吗?
  • 好吧,我在 Reflector 中查看了您的代码 IL,然后意识到我应该很快发布一个修补程序(意思是:没有时间提供带反射代码的正确答案)并认为我只是把它作为一个想法让你深入发现;)
  • 没问题。无论如何都要欣赏它

标签: c# reflection default


【解决方案1】:

我编译了你的代码并在 ILDASM 中加载它并得到了这个

.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
    // Code size       15 (0xf)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.7
    IL_0002:  stfld      int32 dummyCSharp.MyClass::myInt
    IL_0007:  ldarg.0
    IL_0008:  call       instance void [mscorlib]System.Object::.ctor()
    IL_000d:  nop
    IL_000e:  ret
} // end of method MyClass::.ctor

注意ldc.i4.7stfld int32 dummyCSharp.MyClass::myInt 似乎是为 myInt 字段设置默认值的说明。

所以这样的赋值实际上被编译为构造函数中的附加赋值语句。

要检测这样的分配,那么您将需要反射以反映 MyClass 的构造函数方法的 IL 并查找 stfld(设置字段?)命令。


编辑:如果我在构造函数中显式添加一些赋值:

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

    public MyClass()
    {
        myOtherInt = 8;
    }
}

当我在 ILDASM 中加载它时,我得到了这个:

.method public hidebysig specialname rtspecialname 
                instance void  .ctor() cil managed
{
    // Code size       24 (0x18)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.7
    IL_0002:  stfld      int32 dummyCSharp.MyClass::myInt
    IL_0007:  ldarg.0
    IL_0008:  call       instance void [mscorlib]System.Object::.ctor()
    IL_000d:  nop
    IL_000e:  nop
    IL_000f:  ldarg.0
    IL_0010:  ldc.i4.8
    IL_0011:  stfld      int32 dummyCSharp.MyClass::myOtherInt
    IL_0016:  nop
    IL_0017:  ret
} // end of method MyClass::.ctor

请注意,我在 myOtherInt 上添加的额外分配是在调用 Object 类的构造函数之后添加的。

IL_0008:  call       instance void [mscorlib]System.Object::.ctor()

所以你有它,

在 IL 中调用 Object 类的构造函数之前完成的任何赋值都是默认值赋值。

后面的任何内容都是类的实际构造函数代码中的语句。

不过应该​​进行更广泛的测试。

附言这很有趣:-)

【讨论】:

  • 讨厌,但可能是其中的一些东西 - 谢谢。必须深入研究(我还没有对 IL 进行任何反思)。
  • 响应您的更新:酷 - 看起来更加清晰。
  • 我应该有太多时间在我手上。得开始工作了哈哈。
  • 好帖子,但仍然......我会选择可空类型 int?会做需要做的事情:)
  • 更准确的说法是 “在调用 IL 中类的基类的构造函数之前完成的任何赋值都是......”。由于示例类没有显式继承任何东西,它的基类是System.Object
【解决方案2】:

您可能需要为此行为考虑一个可为空的 int:

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

【讨论】:

  • 好建议。不幸的是,我还需要处理引用类型并检测是否提供了 null 默认值。这样我就可以区分强制值和可选值。我投票给你是因为这是一个很好的答案,即使它不是我想要的答案 - 并在 Q 中添加更多信息。
  • 要扩展这种方法,您可以创建两个通用包装结构:Mandatory<T>Optional<T>。您在其中只需要一个公共只读字段(因此它们是不可变的)和两个与T 之间的隐式转换。这可能有点笨拙,但从技术上讲这是一种选择。
【解决方案3】:

默认值是与其他任何值一样的值。没有办法区分这两种情况:

int explicitly = 0;
int implicitly;

在这两种情况下,您都将它们的值设为 0,其中一种方法可以节省您的输入时间。没有神奇的“默认未初始化值”——它们都是零。他们工作起来完全一样。然而,你甚至在考虑这个事实表明你已经严重偏离了好主意的轨道。你在做什么?您的具体需求是什么?你问错问题了;)

【讨论】:

  • 我知道你的意思。但是我认为我确实有一个有效的用例。我正在使用反射来填充来自命令行参数、app.config 甚至其他来源的值。有关更多详细信息,请参阅我的问题更新。使用属性是可行的,但我想消除一定程度的冗余
【解决方案4】:

对于使用可空类型作为可选参数的值类型应该可以工作。如果不是可选的,也可以将字符串初始化为空。

int mandatoryInt;
int? optionalInt;

然而这确实让我觉得有点脏,我会坚持使用属性作为这样做的明确方式。

【讨论】:

  • 谢谢 - 但请参阅我对 Dukeworth 的回复以及我的问题更新。
【解决方案5】:

可能这不是最简单的解决方案...

您可以使用 de DefaultValue 属性来设置值,例如:

导入 System.ComponentModel 和 System.Reflection

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

然后用反射恢复默认值:

PropertyInfo prop = this.GetType().GetProperty("MyNumber");
MessageBox.Show(((DefaultValueAttribute)(prop.GetCustomAttributes(typeof(DefaultValueAttribute), true).GetValue(0))).Value.ToString());

【讨论】:

  • 是的,这就是我已经在做的事情。只是想减少冗余
  • 哦。刚刚添加了一个更通用的解决方案,重新实现 DefaultValueAttribute
【解决方案6】:

如果我想将其构建为通用运行时功能,我会这样做。 对于标量类型,我会创建一个默认值属性并使用它来确定默认性。

这是该任务的部分解决方案 - 我相信它可能会更好,但我只是将其淘汰:

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

【讨论】:

  • 是的,这基本上是我已经拥有的。感谢您不厌其烦地敲响它。但是,它的反射属性方面很好。我只是想看看我是否可以消除它。
  • 严格来说,我目前使用C#语法(初始化器)设置了默认值,但是有一个属性来标记该字段是否可选
【解决方案7】:

创建一个包含值和初始化标志的通用结构怎么样?

public struct InitializationKnown<T> {
    private T m_value;
    private bool m_initialized;

    // the default constructor leaves m_initialized = false, m_value = default(T)
    // InitializationKnown() {}

    InitializationKnown(T value) : m_value(value), m_initialized(true) {}

    public bool initialized { 
        get { return m_initialized; }
    }
    public static operator T (InitializationKnown that) {
        return that.m_value;
    }
    // ... other operators including assignment go here
}

然后只需使用它来代替您需要了解的初始化成员。它是懒惰的未来或承诺的一个非常基本的变体。

【讨论】:

  • 乍一看,这实际上还不错。但是,我认为它不会对属性技术增加太多——它只是将“元数据”从属性移动到包装类——并增加了更多的复杂性。我会投票赞成你提出一个可行的建议,但可能不会走那条路。谢谢。
【解决方案8】:

此方法使用属性获取/设置过程:

    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
    }

一个字段的代码很多 - 你好,sn-ps!

【讨论】:

    【解决方案9】:

    您可以将字段包装在私有/受保护的属性中。如果您想知道它是否已设置,请检查私有字段(例如 _myInt.HasValue())。

    class MyClass
    {
    
        public MyClass()
        {
            myInt = 7;
        }
    
        int? _myInt;
        protected int myInt
        {
            set { _myInt = value; }
            get { return _myInt ?? 0; }
        }
    
        int? _myOtherInt;
        protected int myOtherInt
        {
            set { _myOtherInt = value; }
            get { return _myOtherInt ?? 0; }
        }
    }
    

    【讨论】:

      【解决方案10】:

      如果您想要的是这个,请查看底部的代码。
      它是用 Oxygene[1] 编写的,希望这不是问题。

      [1]或 Delphi Prism 现在如何称呼

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

      输出:

      值:1; inst1HasDefault:真; inst2HasDefault False
      值:2; inst1HasDefault:假; 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;
      

      【讨论】:

        【解决方案11】:

        如果您在分配值之前尝试使用变量,编译器可以设置为生成警告。我有默认设置以及它的行为方式。

        【讨论】:

          【解决方案12】:

          以下是否有帮助:

          bool isAssigned = (myOtherInt == default(int));
          

          【讨论】:

          • 不,抱歉。不错的尝试。在这种情况下,default(int) 将为 0,我可以将其显式初始化。
          猜你喜欢
          • 2011-02-19
          • 2017-01-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-02-27
          • 2023-03-14
          • 1970-01-01
          相关资源
          最近更新 更多