属性构造函数中的Lambdaexpression式

我创build了一个名为RelatedPropertyAttributeAttribute类:

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

我用这个来表示一个类中的相关属性。 我将如何使用它的例子:

 public class MyClass { public int EmployeeID { get; set; } [RelatedProperty("EmployeeID")] public int EmployeeNumber { get; set; } } 

我想使用lambdaexpression式,以便我可以传递一个强types到我的属性的构造函数,而不是一个“魔术string”。 这样我可以利用编译器types检查。 例如:

 public class MyClass { public int EmployeeID { get; set; } [RelatedProperty(x => x.EmployeeID)] public int EmployeeNumber { get; set; } } 

我以为我可以做到这一点,但它是不允许的编译器:

 public RelatedPropertyAttribute<TProperty>(Expression<Func<MyClass, TProperty>> propertyExpression) { ... } 

错误:

非genericstypes“RelatedPropertyAttribute”不能与types参数一起使用

我怎样才能做到这一点?

你不能

  • 你不能创build通用属性types(它是不允许的); 同样,没有定义使用generics属性的语法( [Foo<SomeType>]
  • 你不能在属性初始值设定项中使用lambdaexpression式 – 可用于传递给属性的值是非常有限的,并且不包括expression式(它们非常复杂,是运行时对象,不是编译时文字)

具有通用属性是不可能的。 但是,C#和VB不支持它,但CLR呢。 如果你想写一些IL代码是可能的。

让我们把你的代码:

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

编译代码,用ILSpy或ILDasm打开程序集,然后将内容转储到文本文件。 你的IL属性类声明如下所示:

 .class public auto ansi beforefieldinit RelatedPropertyAttribute extends [mscorlib]System.Attribute 

在文本文件中,然后可以使该属性为通用的。 有几件事情需要改变。

这可以简单地通过更改IL完成,CLR不会抱怨:

 .class public abstract auto ansi beforefieldinit RelatedPropertyAttribute`1<class T> extends [mscorlib]System.Attribute 

现在你可以改变从string到你的genericstypesrelatedProperty的types。

例如:

 .method public hidebysig specialname rtspecialname instance void .ctor ( string relatedProperty ) cil managed 

将其更改为:

 .method public hidebysig specialname rtspecialname instance void .ctor ( !T relatedProperty ) cil managed 

有很多框架来做这样的“脏”工作: Mono.Cecil或CCI 。

正如我已经说过,这不是一个干净的面向对象的解决scheme,但只是想指出另一种方式来打破C#和VB的限制。

有关这个主题的一个有趣的阅读, 看看这本书。

希望能帮助到你。

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

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

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

 public class MyClass { public int EmployeeID { get; set; } [RelatedProperty(nameof(EmployeeID))] public int EmployeeNumber { get; set; } } 

可能的解决方法之一是为每个属性关系定义类并引用它
属性构造函数中的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) { } } } 

你不能。 属性types是有限的写在这里 。 我的build议,尝试从外部评估你的lambdaexpression式,然后使用下列types之一:

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

为了扩大我的评论 ,这是一种用不同的方法来实现你的任务的方法。 你说你想“在一个类中表示相关的属性”,而且“你想使用lambdaexpression式, 这样我就可以传递一个强types到我的属性的构造函数中,而不是一个”魔术string“,这样我可以利用编译器types检查 “。

这里是一种表示编译时input的相关属性的方法,它没有任何魔术string:

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

这是正在考虑的class级。 我们要表明EmployeeIdEmployeeNumber是相关的。 为了简洁一点,我们把这个别名放在代码文件的顶部。 这完全没有必要,但它确实使代码不那么吓人:

 using MyClassPropertyTuple = System.Tuple< System.Linq.Expressions.Expression<System.Func<MyClass, object>>, System.Linq.Expressions.Expression<System.Func<MyClass, object>> >; 

这使得MyClassPropertyTuple成为两个ExpressionTuple的别名,每个Tuple捕获从MyClass到一个对象的函数的定义。 例如, MyClass上的属性获取器就是这样的函数。

现在我们来捕捉关系。 这里我已经在MyClass上做了一个静态索引,但是这个列表可以在任何地方定义:

 public class MyClass { public static List<MyClassPropertyTuple> Relationships = new List<MyClassPropertyTuple> { new MyClassPropertyTuple(c => c.EmployeeId, c => c.EmployeeNumber) }; } 

C#编译器知道我们正在构造一个Expression Tuple ,所以我们不需要在这些lambdaexpression式之前进行任何明确的转换 – 它们会自动进入Expression s。

这基本上就是定义 – EmployeeIdEmployeeNumber提到的是在编译时强制types化和强制执行的,重命名属性的重构工具应该能够在重命名时find这些用法(ReSharper绝对可以)。 这里没有魔法string。


当然,我们也希望能够在运行时询问关系(我假设!)。 我不知道你想怎么做,所以这段代码只是说明。

 class Program { static void Main(string[] args) { var propertyInfo1FromReflection = typeof(MyClass).GetProperty("EmployeeId"); var propertyInfo2FromReflection = typeof(MyClass).GetProperty("EmployeeNumber"); var e1 = MyClass.Relationships[0].Item1; foreach (var relationship in MyClass.Relationships) { var body1 = (UnaryExpression)relationship.Item1.Body; var operand1 = (MemberExpression)body1.Operand; var propertyInfo1FromExpression = operand1.Member; var body2 = (UnaryExpression)relationship.Item2.Body; var operand2 = (MemberExpression)body2.Operand; var propertyInfo2FromExpression = operand2.Member; Console.WriteLine(propertyInfo1FromExpression.Name); Console.WriteLine(propertyInfo2FromExpression.Name); Console.WriteLine(propertyInfo1FromExpression == propertyInfo1FromReflection); Console.WriteLine(propertyInfo2FromExpression == propertyInfo2FromReflection); } } } 

这里我编写了propertyInfo1FromExpressionpropertyInfo2FromExpression的代码,在debugging的时候明智地使用了Watch窗口 – 这通常是我如何计算Expression树实际上包含的内容。

运行这将产生

 EmployeeId EmployeeNumber True True 

表明我们可以成功地提取相关属性的细节,并且(关键地) 它们与通过其他方式获得的PropertyInfo是相关的 。 希望你可以使用这个和你实际使用的任何方法在运行时指定感兴趣的属性。