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

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

public virtual void NotifyOfPropertyChange(Expression<Func> property); 

我有一个自定义属性,我想在构造函数中使用相同类型的编译器检查属性名称,以使我能够键入:

 [MyCustomAttribute(() => PropertyName)] 

代替:

 [MyCustomAttribute("PropertyName")] 

使用构造函数定义:

 public MyCustomAttribute(params Expression<Func>[] properties) 

但是,由于属性参数的限制是常量表达式,这似乎是不可能的。

任何人都可以推荐一种不同的方法,我可以让编译器检查我的属性参数中的属性名称,而不是留下这个只使用字符串的潜在错误?

编辑:感谢Marc的回答,我现在已经实现了这个:

 #if DEBUG foreach (var propertyInfo in GetType().GetProperties().Where(propertyInfo => Attribute.IsDefined(propertyInfo, typeof (MyCustomAttribute)))) { foreach (var propertyName in propertyInfo.GetAttributes(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 

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

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

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

解决方案1

您可以使用PostSharp方面并使用CompileTimeInitialize来读取属性名称。

例如:

 [Serializable] class MyCustomAttribute : LocationLevelAspect { string propertyName; public override void CompileTimeInitialize( LocationInfo targetLocation, AspectInfo aspectInfo ) { this.propertyName = targetLocation.PropertyName; } } 

此function存在于免费的PostSharp社区版中。

问题是使用System.Reflection看不到以这种方式构建的自定义属性。

解决方案2

您还可以使用添加自定义属性的方面。 然后,该方面应实现IAspectProvider并返回CustomAttributeIntroductionAspect的实例。 您可以从此页面获得灵感。 PostSharp专业版($)提供此function。

解决方案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(如果您愿意)。