How to pass object names with compiler check / Expression tree to user attribute

In several places, I noticed that expression trees were passed as arguments to methods that let you check the names of objects. 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 that I would like to have the same type of property name compiler check in the constructor so that I can type:

 [MyCustomAttribute(() => PropertyName)] 

Instead:

 [MyCustomAttribute("PropertyName")] 

Using constructor definition in strings:

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

However, due to the restriction on the parameters of the attribute, which is constant expressions, this seems impossible.

Can someone recommend a different approach when I can get the compiler to check the property names in the parameters of my attribute and not leave this potential error that uses only strings?

Edit: thanks Marc 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 
+4
source share
2 answers

It is simply not possible. Attributes are limited to very basic types that do not include what you need for this. One possible static safe way to do this is to subclass an attribute into a property , but this is an insane amount of work.

Personally, I just write unit test, which will find all occurrences of the attribute and verify that they are reasonable using reflection. You can also do this in the main code inside the #if DEBUG (or similar) block.

+4
source

There are several solutions using PostSharp (disclaimer: I'm human), some of which have a free version.

Solution 1

You can use the 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 featured in the free PostSharp Community Edition.

The catch is that a custom attribute constructed in this way is not displayed using System.Reflection.

Decision 2

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

Decision 3

You can also create your own attribute class (any class, not a specific aspect) that implements the IValidableAnnotation interface:

 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 with the free version of PostSharp, and you can easily include it in the # if / # endif block to make your code completely independent of PostSharp if you want.

+3
source

Source: https://habr.com/ru/post/1333178/


All Articles