【问题标题】:How to get the name of a property of type myClass?如何获取 myClass 类型的属性的名称?
【发布时间】:2009-06-15 20:06:12
【问题描述】:

我有一个实现多个属性的自定义类(调用 i 字段)。属性之一是MaximumLength,它指定值可以是的最大长度。 Value 属性是一个对象,因此我可以设置为 string、int、double 等。然后我有一个类,其中包含多个 Field 类型的属性。所有 Field 属性都在构造函数中初始化,并且只能写入 Field.Value 属性。如果尝试将 Field.Value 设置为对于该字段来说太长的值并实现 INotifyPropertyChanged,我想要做的是抛出一个错误。我的问题是 Value 属性是通用 Field 类的成员,我不知道如何获取该类中的属性名称。

一个例子:

public class Customer
{
   private Field _firstName = new Field(typeof(string), 20);

   public Field FirstName
   {
      get
      {
         return _firstName;
      }
   }
}

public class Field
{
   private Type _type;
   private int _maximumLength;
   object _value;

   public Field(Type type, int maximumLength)
   {
      _type = type;
      _maximumLength = maximumLength;
   }         

   public Object Value
   {
      get
      {
         return _value;
      }
      set
      {
         if (value.ToString().Length > _maximumLength)
         {
            throw(string.Format("{0} cannot exceed {1} in length.", property name, _maximumValue);
         }
         else
         {
            _value = value;
            OnPropertyChanged(property name);
         }
      }
   }
}

希望这已经足够清楚了。

【问题讨论】:

  • 最接近这一点的是实际属性的上下文访问,字段本身不会知道它的起源,毕竟-它只是一个实例..上下文的东西-即..我访问此属性以获取此字段,然后打开反射将是非常限制性的 - 与此类似的系统,如 WPF 的依赖系统证明,在这种情况下,没有比简单地以字符串形式复制名称更好的方法。或者,您可以研究诸如 postsharp 之类的面向方面的系统,它可以让您在编译时重写 IL。

标签: c# .net


【解决方案1】:

在我看来,是的,可能有一种方法可以使用反射来获取这些信息,但我不确定这是否是万无一失的方法,并且可能会在以后让您进入更繁琐的调试阶段.只是看起来太……聪明了。

在我看来,根据我过去实施的做法,Fredrik 和 Micahtan 为您指明了正确的方向。您的 Field 类应该实现一个 Name 属性,在实例化时设置。我可以指出这是一个好主意的一个原因是,这是微软做同样事情的方式。如果您查看为任何可视化设计器生成的代码,控件会实现由设计器设置的 Name 属性。如果有一种万无一失的方法可以在幕后做到这一点,那么您必须相信会做到这一点。

使用 Name 属性的另一个好处是,它允许您为您的属性提供“英文”翻译。即“名字”而不是“名字”,这是一种更友好的用户界面方法,并将用户与您的实现分离(以重载“分离”一词)。

【讨论】:

    【解决方案2】:

    为什么不简单地将 Field 类扩展为也包含一个名称,并在构造函数中传递它?

    private Field _firstName = new Field(typeof(string), "FirstName", 20);
    

    Field 类看起来像这样:

    public class Field
    {
       private Type _type;
       private int _maximumLength;
       private string _name;
       object _value;
    
       public Field(Type type, string name, int maximumLength)
       {
          _type = type;
          _maximumLength = maximumLength;
          _name = name;
       }         
    
       public Object Value
       {
          get
          {
             return _value;
          }
          set
          {
             if (value.ToString().Length > _maximumLength)
             {
                throw new SomeException(string.Format("{0} cannot exceed {1} in length.", _name, _maximumValue));
             }
             else
             {
                _value = value;
                OnPropertyChanged(_name);
             }
          }
       }
    }
    

    【讨论】:

    • 我已经考虑过这一点,但对我来说这似乎是多余的,因为该属性有一个名称,如果我或其他人开始重命名属性,还有一个额外的步骤。如果我无法找到另一种方法,我可能会依靠它。
    【解决方案3】:

    同意 Fredrik。

    但是,如果您想在您的 Field 数据中具有某种类型安全性,您也应该考虑使用泛型,即将您的 Field 类重新定义为存储 T _value 而不是对象的 Field。

    我还假设您必须有不同的算法来检查不同类型的长度(例如字符串与数字等)以及空值检查等。

    HTH。

    【讨论】:

      【解决方案4】:

      我同意上述观点。该字段应具有自己的 Name 属性,该属性通过构造设置。

      我不同意使用泛型来实现这一点。类型安全是一个很好的目标,但您可能还需要能够存储 不同类型的 Field 对象的集合/列表

      如果没有泛型类也实现公共接口,泛型将不允许您这样做。

      你可以实现一个通用接口,但是你需要 Value 是对象类型,而不是 类型,这样你就失去了泛型的性能优势。

      【讨论】:

      • 我同意,如果需要一个 Field 的集合,那么泛型可能不是可行的方法,或者肯定需要添加接口以使泛型实现工作。但是,如果 Field 只是用于封装一些验证逻辑,那么我相信泛型可能是合适的和首选的。
      【解决方案5】:

      有很多方法可以做你想做的事,但没有一个是特别优雅或万无一失的。

      例如,您可以在检测到错误情况时遍历 Field 类中的调用堆栈,并假设始终通过从对象中检索 Field 来调用它。这可能并不总是正确的......

      或者,您可以在初始化字段时使用 LINQ 表达式将字段名称传递给 Field 对象。 Marc Gravell 有一个technique that's described here。在你的情况下,这可能也不完美。

      最终,使用更简单、不太复杂的实现可能会更好。

      最后,你应该考虑一下这是否真的是重复而不是不必要的耦合。为什么你的代码发出的消息应该与其中的属性或方法的名称相关联?如果您需要在将来某个时候将您的代码国际化怎么办?如果客户希望消息更清晰或更紧密地与业务领域保持一致怎么办……您是否愿意重构代码以适应此类更改?

      【讨论】:

      • 遍历堆栈将不起作用,因为 value 属性不是通过“named”属性访问的。这已经从堆栈中弹出,您无法从属性内访问它。另外,我认为关键是 INotifyPropertyChanged 的​​实现来为其填充事件 arg。
      【解决方案6】:

      我相信这可以实现您想要发生的事情。我在类中添加了泛型,而不是使用对象,以增加类型安全。我还添加了一个显式异常类型,以便它可以编译,但异常类型可以是任何带字符串的类型。

      public class Customer
      {
          private Field<string> _firstName = new Field<string>("FirstName", 20);
      
          public Field<string> FirstName
          {
              get
              {
                  return _firstName;
              }
          }
      }
      
      public class Field<T>
      {
          private int _maximumLength;
          private T _value;
          private string _propertyName;
      
          public Field(string propertyName, int maximumLength)
          {
              _propertyName = propertyName;
              _maximumLength = maximumLength;
          }
      
          public T Value
          {
              get
              {
                  return _value;
              }
              set
              {
                  if (value.ToString().Length > _maximumLength)
                  {
                      throw new ArgumentException(string.Format("{0} cannot exceed {1} in length.", _propertyName, _maximumLength));
                  }
                  else
                  {
                      _value = value;
                      OnPropertyChanged(_propertyName);
                  }
              }
          }
      }
      

      编辑:

      回复您的评论:“我已经考虑过这一点,但对我来说这似乎是多余的,因为该属性有一个名称,并且如果我或其他人开始重命名属性,我可能会退回到这一点,但如果我无法找到另一种方法。”

      我相信您要求的是某种反射代码,当从 Field 通知属性更改时,它将在 Customer 中检索属性的名称。我不相信有任何代码可以可靠地做到这一点。考虑:

      Field myFirstNameField = myCustomer.FirstName;
      myFirstNameField.Value = "X";
      

      这里的属性名称应该是什么? Field 的单个实例现在在两个单独的范围内有两个标识符。也许有一种方法可以撤回引用您的对象的标识符列表(尽管如果可以的话,我不知道如何),但是您将如何选择您想要的?如果您想以这种方式实现 PropertyChangeNotification,这就是我建议使用构造函数“注入”属性名称的原因。

      【讨论】:

        【解决方案7】:

        这是解决您正在尝试做的事情的一种方法:

                Customer c1 = new Customer();
                c1.FirstName.Value = "Me";
        
                Type t = c1.GetType();
                MemberInfo[] infos = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance);
        
                foreach (MemberInfo info in infos)
                {
                    if (info.MemberType.ToString().Contains("Field"))
                    {
                        Console.WriteLine("Found member {0} of type {1}", info.Name, info.MemberType);
                    }
                }
        

        但我同意使用模板化解决方案会更好。

        【讨论】:

        • 这不是他要问的......他正在尝试获取用于访问它的属性的名称。
        【解决方案8】:

        您想要做的事情并不像您设计的那样真正可行。看起来应该是这样,但实际上这两个类之间并没有太大的关系。

        我只会添加 name 属性并将其更改为通用类,因为步行堆栈根本没有效率,但要尝试回答您的问题,您可以这样做以获得大部分内容求...

        如果您包含字段类并创建了实际类型的属性,您可以遍历堆栈以获取属性名称(如果您添加了许多其他人建议使其成为泛型类的更改,它会使它成为一个更好的解决方案,因为您不需要投射任何东西)。

        如果你这样写客户类:

        public class Customer
        {
            private Field _firstName = new Field(typeof(string), 20);
        
            public string FirstName
            {
                get
                {
                    return _firstName.Value as string;
                }
                set
                {
                    _firstName.Value = value;
                }
            }
        }
        

        这将允许您遍历堆栈以获取字段类中的调用方法名称(在本例中为属性)

        string name = "<unknown>";
        StackTrace st = new StackTrace();
        name = st.GetFrame(1).GetMethod().Name;
        

        name 现在将具有值

        set_FirstName

        所以你只需要去掉 set_ 来获取属性名称,但是如果你使用 INotifyPropertyChanged 事件,你仍然会遇到问题,因为发送者将是 Field 对象而不是客户对象。

        【讨论】:

          【解决方案9】:

          我现在确信我无法从这里到达那里。在我看来,如果 a 有一个 Field 类型的属性,那么询问 Field 你的属性名称是什么应该很简单。很明显,我会试图变得太聪明而避免只做一点工作。我将在构造函数中添加一个属性名称。该字段永远不会在一个类之外创建,因此如果属性名称更改,则必须在两个地方进行更改可能会很不方便。我并不真正担心使属性名称对客户友好或全球化,因为该错误意味着其他程序员试图将太长的字符串(或转换为字符串的数字太大)放入字段中。我喜欢泛型建议,但还没有弄清楚如何使用它并且仍然能够将所有字段放入列表中。最终我想以特定顺序迭代列表并构建一个类似于 field1=value1|field2=value2|etc 的单个字符串。

          【讨论】:

            猜你喜欢
            • 2010-10-14
            • 2021-12-04
            • 1970-01-01
            • 1970-01-01
            • 2019-05-16
            • 1970-01-01
            • 2010-09-14
            • 2014-06-27
            • 2012-01-08
            相关资源
            最近更新 更多