属性构造函数中的Lambda表达式

Dav*_*New 40 .net c# reflection attributes custom-attributes

我创建了一个Attribute名为的类RelatedPropertyAttribute:

[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute: Attribute
{
    public string RelatedProperty { get; private set; }

    public RelatedPropertyAttribute(string relatedProperty)
    {
        RelatedProperty = relatedProperty;
    }
}
Run Code Online (Sandbox Code Playgroud)

我用它来表示类中的相关属性.我将如何使用它的示例:

public class MyClass
{
    public int EmployeeID { get; set; }

    [RelatedProperty("EmployeeID")]
    public int EmployeeNumber { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我想使用lambda表达式,以便我可以将强类型传递给我的属性的构造函数,而不是"魔术字符串".这样我可以利用编译器类型检查.例如:

public class MyClass
{
    public int EmployeeID { get; set; }

    [RelatedProperty(x => x.EmployeeID)]
    public int EmployeeNumber { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我以为我可以使用以下内容,但编译器不允许这样做:

public RelatedPropertyAttribute<TProperty>(Expression<Func<MyClass, TProperty>> propertyExpression)
{ ... }
Run Code Online (Sandbox Code Playgroud)

错误:

非泛型类型"RelatedPropertyAttribute"不能与类型参数一起使用

我怎样才能做到这一点?

cod*_*res 54

具有通用属性是不可能以传统方式进行的.但是C#和VB不支持它,但CLR支持它.如果你想写一些IL代码,那就有可能.

我们来看看你的代码:

[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute: Attribute
{
    public string RelatedProperty { get; private set; }

    public RelatedPropertyAttribute(string relatedProperty)
    {
       RelatedProperty = relatedProperty;
    }
}
Run Code Online (Sandbox Code Playgroud)

编译代码,开拓装配与ILSpyILDASM,然后倾倒内容到一个文本文件中.属性类声明的IL将如下所示:

.class public auto ansi beforefieldinit RelatedPropertyAttribute
extends [mscorlib]System.Attribute
Run Code Online (Sandbox Code Playgroud)

在文本文件中,您可以使该属性通用.有几件事需要改变.

这可以通过更改IL来完成,而CLR不会抱怨:

.class public abstract auto ansi beforefieldinit
      RelatedPropertyAttribute`1<class T>
      extends [mscorlib]System.Attribute
Run Code Online (Sandbox Code Playgroud)

现在您可以将relatedProperty的类型从字符串更改为通用类型.

例如:

.method public hidebysig specialname rtspecialname 
    instance void .ctor (
        string relatedProperty
    ) cil managed
Run Code Online (Sandbox Code Playgroud)

将其更改为:

.method public hidebysig specialname rtspecialname 
    instance void .ctor (
        !T relatedProperty
    ) cil managed
Run Code Online (Sandbox Code Playgroud)

有很多框架可以完成这样的"脏"工作:Mono.CecilCCI.

正如我已经说过的那样,它不是一个干净的面向对象的解决方案,而只是想指出另一种方法来打破C#和VB的限制.

围绕这个主题有一个有趣的阅读,请查看本书.

希望能帮助到你.

  • 为疯狂的科学家方法+1,永远不要说永远 (28认同)

Mar*_*ell 33

你不能

  • 你不能创建通用的属性类型(它是不允许的); 同样,没有定义使用通用属性([Foo<SomeType>])的语法
  • 你不能在属性初始化器中使用lambdas - 可用于传递给属性的值非常有限,并且根本不包含表达式(它们非常复杂,并且是运行时对象,而不是编译时文字)

  • 顺便说一下,Mark你知道为什么我们不能将Decimals作为参数传递给Attributes吗?我从未遇到过这种限制背后的解释.*编辑*:找到[使用十进制值作为c#中的属性参数?](http://stackoverflow.com/questions/507528/use-decimal-values-as-attribute-params-in-c) (4认同)
  • @Ilya因为IL不知道小数,即使C#有.根本没有直接的IL机制来在元数据中表示它们. (3认同)
  • @IlyaIvanov:确实这是一个单独的问题,但幸运的是它已经被问过:http://stackoverflow.com/questions/3192833/why-decimal-is-not-a-valid-attribute-parameter-type (2认同)

Aym*_*man 13

如果您使用的是C#6.0,则可以使用nameof

用于获取变量,类型或成员的简单(非限定)字符串名称.在报告代码中的错误,连接模型 - 视图 - 控制器(MVC)链接,触发属性更改事件等时,通常需要捕获方法的字符串名称.使用nameof有助于在重命名定义时保持代码有效.之前必须使用字符串文字来引用定义,这在重命名代码元素时很脆弱,因为工具不知道检查这些字符串文字.

有了它,你可以像这样使用你的属性:

public class MyClass
{
    public int EmployeeID { get; set; }

    [RelatedProperty(nameof(EmployeeID))]
    public int EmployeeNumber { get; set; }
}
Run Code Online (Sandbox Code Playgroud)


Gen*_*hak 8

一种可能的解决方法是为每个属性关系定义类,并通过
属性构造函数中的typeof()运算符引用它.

更新:

例如:

[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute : Attribute
{
    public Type RelatedProperty { get; private set; }

    public RelatedPropertyAttribute(Type relatedProperty)
    {
        RelatedProperty = relatedProperty;
    }
}

public class PropertyRelation<TOwner, TProperty>
{
    private readonly Func<TOwner, TProperty> _propGetter;

    public PropertyRelation(Func<TOwner, TProperty> propGetter)
    {
        _propGetter = propGetter;
    }

    public TProperty GetProperty(TOwner owner)
    {
        return _propGetter(owner);
    }
}

public class MyClass
{
    public int EmployeeId { get; set; }

    [RelatedProperty(typeof(EmployeeIdRelation))]
    public int EmployeeNumber { get; set; }

    public class EmployeeIdRelation : PropertyRelation<MyClass, int>
    {
        public EmployeeIdRelation()
            : base(@class => @class.EmployeeId)
        {

        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Fra*_*isi 5

你不能.属性类型受限于此处所写.我的建议是,尝试从外部评估你的lambda表达式,然后使用以下类型之一:

  • 简单类型(bool,byte,char,short,int,long,float和double)
  • 系统类型
  • 枚举
  • object(对象类型的属性参数的参数必须是上述类型之一的常量值.)
  • 任何上述类型的一维阵列