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

发布于 2024-10-09 02:02:48 字数 1457 浏览 9 评论 0原文

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

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

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

[MyCustomAttribute(() => PropertyName)]

而不是:

[MyCustomAttribute("PropertyName")]

使用构造函数定义大致如下:

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

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

谁能推荐一种不同的方法,让编译器检查属性参数中的属性名称,而不是在仅使用字符串的情况下留下这个潜在的错误?

编辑:感谢马克的回答,我现在已经实现了:

#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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

二手情话 2024-10-16 02:02:48

这根本不可能。属性仅限于非常基本的类型,不包括您为此所需的内容。一种可能的静态安全方法是每个属性对属性进行子类化,但这是一项巨大的工作量。

就我个人而言,我只是编写一个单元测试来查找该属性的所有出现并通过反射检查它们是否合理。您还可以在 #if DEBUG 块(或类似块)内的主代码中执行此操作。

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).

似狗非友 2024-10-16 02:02:48

有几种使用 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 并返回 CustomAttributeIntroductionAspect 的实例。您可以从 此页面。 PostSharp 专业版 ($) 提供此功能。

解决方案 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。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文