How to pass compiler checked property names / Expression tree to a custom attribute

别说谁变了你拦得住时间么 提交于 2019-12-12 08:14:40

问题


In a few places, I've noticed expression trees passed as arguments to methods to allow compiler checking of property names. For example, Caliburn Micro has the following method signature in its PropertyChangedBase class:

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

I have a custom attribute which I would like to have the same type of compiler checking of property names in the constructor, to enable me to type:

[MyCustomAttribute(() => PropertyName)]

Instead of:

[MyCustomAttribute("PropertyName")]

Using a constructor definition along the lines of:

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

However, due to the restriction on Attribute parameters being constant expressions, this appears not to be possible.

Can anyone recommend a different approach where I can get the compiler to check property names in my attribute parameters rather than leaving this potential bug where only strings are used?

Edit: Thanks to Marc's answer, I have implemented this for now:

#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

回答1:


This is simply not possible. Attributes are limited to very basic types that don't include what you would need for this. One possible static-safe way of doing it would be to subclass the attribute per property but that is an insane amount of work.

Personally, I'd just write a unit test that finds all occurrences of the attribute and checks they are sensible via reflection. You could also do this in the main code inside a #if DEBUG block (or similar).




回答2:


There are several solutions using PostSharp (disclaimer: I am the man), some of which with the free edition.

Solution 1

You could use a PostSharp aspect and use CompileTimeInitialize to read the property name.

For instance:

[Serializable]
class MyCustomAttribute : LocationLevelAspect
{
  string propertyName;

  public override void  CompileTimeInitialize( LocationInfo targetLocation,
                                               AspectInfo aspectInfo )
  {
      this.propertyName = targetLocation.PropertyName;
  }
}  

This feature is present in the free PostSharp Community Edition.

The catch is that a custom attribute built this way is not visible using System.Reflection.

Solution 2

You can also use an aspect that adds a custom attribute. The aspect should then implement IAspectProvider and return instances of CustomAttributeIntroductionAspect. There's an example you can get inspired from on this page. This feature is available on PostSharp Professional Edition ($).

Solution 3

You can also make your custom attribute class (any class, not specifically an aspect) implement the interface 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;
       }
    }
}

This is possible using the free edition of PostSharp and you can easily include it in an #if/#endif block to make your code fully independent from PostSharp if you wish.



来源:https://stackoverflow.com/questions/4518291/how-to-pass-compiler-checked-property-names-expression-tree-to-a-custom-attrib

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!