C# 如何将编译器检查的属性名/表达式树传递给自定义属性
在一些地方,我注意到表达式树作为参数传递给方法,以允许编译器检查属性名。例如,Caliburn Micro在其PropertyChangedBase类中具有以下方法签名: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")] 使用构造函数定义时应
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;
}
}
}