C# 如何将编译器检查的属性名/表达式树传递给自定义属性

C# 如何将编译器检查的属性名/表达式树传递给自定义属性,c#,expression-trees,custom-attributes,C#,Expression Trees,Custom Attributes,在一些地方,我注意到表达式树作为参数传递给方法,以允许编译器检查属性名。例如,Caliburn Micro在其PropertyChangedBase类中具有以下方法签名: public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property); 而不是: [MyCustomAttribute("PropertyName")] 使用构造函数定义时应

在一些地方,我注意到表达式树作为参数传递给方法,以允许编译器检查属性名。例如,Caliburn Micro在其PropertyChangedBase类中具有以下方法签名:

public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property);
而不是:

[MyCustomAttribute("PropertyName")]
使用构造函数定义时应遵循以下几点:

public MyCustomAttribute(params Expression<Func<object>>[] properties)
public MyCustomAttribute(参数表达式[]属性)
但是,由于属性参数是常量表达式的限制,这似乎是不可能的

有谁能推荐一种不同的方法,让编译器检查属性参数中的属性名,而不是只使用字符串的情况下留下这个潜在的错误

编辑:多亏了Marc的回答,我现在已经实现了:

#if DEBUG
            foreach (var propertyInfo in
                GetType().GetProperties().Where(propertyInfo => Attribute.IsDefined(propertyInfo, typeof (MyCustomAttribute))))
            {
                foreach (var propertyName in propertyInfo.GetAttributes<MyCustomAttribute>(true)
                    .SelectMany(attribute => attribute.PropertyNames))
                    Debug.Assert(
                        GetType().GetProperty(propertyName) != null,
                        "Property not found",
                        "Property {0} declared on attributed property {1} was not found on type {2}.",
                        propertyName, propertyInfo.Name, GetType().Name
                    );
            }
#endif
#如果调试
foreach(var propertyInfo在
GetType().GetProperties().Where(propertyInfo=>Attribute.IsDefined(propertyInfo,typeof(MyCustomAttribute)))
{
foreach(propertyInfo.GetAttributes中的var propertyName(true)
.SelectMany(属性=>attribute.PropertyNames))
断言(
GetType().GetProperty(propertyName)!=null,
“未找到属性”,
“在类型{2}上找不到属性属性{1}上声明的属性{0}。”,
propertyName,propertyInfo.Name,GetType().Name
);
}
#恩迪夫

这根本不可能。属性仅限于非常基本的类型,不包括您需要的类型。一种可能的静态安全方法是为每个属性子类化属性,但这是一个疯狂的工作量


就我个人而言,我只需要编写一个单元测试来查找属性的所有出现,并通过反射检查它们是否合理。您也可以在
#if DEBUG
块(或类似块)中的主代码中执行此操作。

使用PostSharp有几种解决方案(免责声明:我就是男人),其中一些是免费版本

解决方案1 您可以使用PostSharp特性并使用CompileTimeInitialize读取属性名称

例如:

[Serializable]
class MyCustomAttribute : LocationLevelAspect
{
  string propertyName;

  public override void  CompileTimeInitialize( LocationInfo targetLocation,
                                               AspectInfo aspectInfo )
  {
      this.propertyName = targetLocation.PropertyName;
  }
}  
此功能在免费PostSharp社区版中提供

问题是,使用System.Reflection以这种方式构建的自定义属性不可见

解决方案2 还可以使用添加自定义属性的特性。然后,方面应该实现IAspectProvider并返回CustomAttributeIntroductionSpect的实例。有一个例子你可以从中得到启发。此功能在PostSharp Professional Edition($)上提供

解决方案3 您还可以使自定义属性类(任何类,而不是特定的方面)实现接口IValidableAnnotation:

public class MyAttribute : Attribute, IValidableAnnotation
{
    private string propertyName;

    public MyAttribute(string propertyName)
    {
       this.propertyName = propertyName;
    }

    public bool CompileTimeValidate( object target )
    {
       PropertyInfo targetProperty = (PropertyInfo) target;
       if ( targetProperty.Name != propertyName )
       {
          Message.Write( Severity.Error, "MY001", 
             "The custom attribute argument does not match the property name.");
          return false;
       }
    }
}

这可以使用PostSharp的免费版本,您可以轻松地将其包含在#if/#endif块中,以使您的代码完全独立于PostSharp(如果您愿意)。

正如我所怀疑的那样。谢谢你的确认和提示。现在,我已经实现了问题编辑中所示的功能。谢谢你,盖尔。我今天以前没见过波斯夏普。我将把它添加到我的圣诞研究清单中。
public class MyAttribute : Attribute, IValidableAnnotation
{
    private string propertyName;

    public MyAttribute(string propertyName)
    {
       this.propertyName = propertyName;
    }

    public bool CompileTimeValidate( object target )
    {
       PropertyInfo targetProperty = (PropertyInfo) target;
       if ( targetProperty.Name != propertyName )
       {
          Message.Write( Severity.Error, "MY001", 
             "The custom attribute argument does not match the property name.");
          return false;
       }
    }
}