Cómo pasar los nombres de propiedad / árbol de expresión comprobados del comstackdor a un atributo personalizado

En algunos lugares, he notado que los árboles de expresiones pasaron como argumentos a métodos para permitir la comstackción de nombres de propiedades. Por ejemplo, Caliburn Micro tiene la siguiente firma de método en su clase PropertyChangedBase:

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

Tengo un atributo personalizado que me gustaría tener el mismo tipo de comprobación de comstackdor de nombres de propiedades en el constructor, para que pueda escribir:

 [MyCustomAttribute(() => PropertyName)] 

En lugar de:

 [MyCustomAttribute("PropertyName")] 

Usando una definición de constructor a lo largo de las líneas de:

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

Sin embargo, debido a que la restricción en los parámetros del atributo son expresiones constantes, esto parece no ser posible.

¿Alguien puede recomendar un enfoque diferente donde pueda hacer que el comstackdor verifique los nombres de las propiedades en mis parámetros de atributos en lugar de dejar este error potencial donde solo se usan cadenas?

Edit: Gracias a la respuesta de Marc, he implementado esto por ahora:

 #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 

Esto simplemente no es posible. Los atributos están limitados a tipos muy básicos que no incluyen lo que necesitarías para esto. Una posible forma de hacerlo con seguridad estática sería subclasificar el atributo por propiedad, pero esa es una cantidad insana de trabajo.

Personalmente, acabo de escribir una prueba de unidad que encuentra todas las apariciones del atributo y comprueba que son sensibles a través de la reflexión. También puede hacer esto en el código principal dentro de un bloque #if DEBUG (o similar).

Hay varias soluciones que utilizan PostSharp (descargo de responsabilidad: Yo soy el hombre), algunas de las cuales con la edición gratuita.

Solución 1

Podría usar un aspecto PostSharp y usar CompileTimeInitialize para leer el nombre de la propiedad.

Por ejemplo:

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

Esta característica está presente en la edición gratuita de la Comunidad PostSharp.

El problema es que un atributo personalizado creado de esta manera no es visible usando System.Reflection.

Solucion 2

También puede utilizar un aspecto que agrega un atributo personalizado. El aspecto debe implementar IAspectProvider y devolver instancias de CustomAttributeIntroductionAspect. Hay un ejemplo del que puedes inspirarte en esta página . Esta característica está disponible en PostSharp Professional Edition ($).

Solucion 3

También puede hacer que su clase de atributo personalizado (cualquier clase, no específicamente un aspecto) implemente la interfaz 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; } } } 

Esto es posible utilizando la edición gratuita de PostSharp y puede incluirlo fácilmente en un bloque # if / # endif para que su código sea completamente independiente de PostSharp si lo desea.