2017-04-19 55 views
1

我需要一个有效的方法来总结相同类型的两个对象中的任意属性。C#表达式来添加两个对象的属性值

我有一个类具有大量不同数值类型的属性:

public class MyClass 
{ 
    public int Field1 { get; set; } 
    public long Field2 { get; set; } 
    public float Field3 { get; set; } 
    public double Field4 { get; set; } 
    //... 
    public uint Field100 { get; set; } 
} 

在运行时,我允许用户选择这些字段的任意子集:

List<PropertyInfo> props = new List<PropertyInfo>();//Field1, Field5, Field99 etc... 

我然后需要迭代两个对象上的所有选定属性,将它们相加,并将其重新分配给第一个对象:

MyClass mc1 = new MyClass(); 
MyClass mc2 = new MyClass(); 

SumProps(mc1, mc2, props); 

这对于使用字段1,5和99上面的例子中,将具有这样的效果:我目前使用的反射与PropertyInfo.GetValue()/SetValue()并手动浇铸到合适的类型

mc1.Field1 += mc2.Field1; 
mc1.Field5 += mc2.Field5; 
mc1.Field99 += mc2.Field99; 

。这太慢了,因为这是将被称为数十亿次的代码的性能关键部分。

因此,我需要一种生成表达式Lambda的方法,该方法将使用适当的类型生成汇总所有请求字段的代码。然后,我将调用拉姆达,如:

MySumPropsLambda(c1, c2); 

从我研究过它会涉及BlockExpressionExpression.AddAssign,但我不能完全换我围绕如何真正完成它的头。

我使用Visual Studio 2013和.NET 4.5.1

编辑:得益于以下阿卡什卡瓦,我做了轻微的修改,现在用这个解决方案:

public static Action<T, T> MakePropertySummationAction<T>(PropertyInfo[] props) 
    { 
     var mc1 = Expression.Parameter(typeof(T)); 
     var mc2 = Expression.Parameter(typeof(T)); 
     var exps = new List<Expression>(); 

     foreach (var pi in props) 
     { 
      var p1 = Expression.Property(mc1, pi.Name); 
      var p2 = Expression.Property(mc2, pi.Name); 
      exps.Add(Expression.AddAssign(p1, p2)); 
     } 

     var blockExpr = Expression.Block(exps); 
     return Expression.Lambda<Action<T, T>>(blockExpr, mc1, mc2).Compile(); 
    } 

打印块表达式字符串证明它生成了所需的代码:

(Param_0.Field1 += Param_1.Field1) 
(Param_0.Field2 += Param_1.Field2) 
(Param_0.Field3 += Param_1.Field3) 
(Param_0.Field4 += Param_1.Field4) 

执行一百万次求和的性能毫秒:

Direct took 4.8ms 
Compiled lambda took 177.5ms 
Reflection took 5376.7ms 
+1

我不知道如果我理解这个问题完全因此评论,而不是一个答案,但出于好奇,为什么你不能用正确的数据结构如字典做到这一点? –

+0

代码味道警报!具有大量数值属性的类不是一个好主意。你可以使用数组的值吗? – ja72

+0

你可以尝试使用像T4这样的代码生成器来生成你想要的'Add'方法。您必须使用反射来告诉生成器如何在构建时扫描类型的属性,但是在运行时不会有任何开销。 – JamesFaix

回答

2

工作拨弄

https://dotnetfiddle.net/xPrXXG

mc1.Field1 += mc2.Field2; 

Lambda表达式相当于是....

Action<MyClass,MyClass> CreateMethod(string propertyName){ 

    var mc1 = Expression.Parameter(typeof(MyClass)); 
    var mc2 = Expression.Parameter(typeof(MyClass)); 
    var p1 = Expression.Property(mc1, propertyName); 
    var p2 = Expression.Property(mc2, propertyName); 

    var assign = Expression.AddAssign(p1,p2); 

    return Expression.Lambda<Action<MyClass,MyClass>> 
      (assign,mc1,mc2).Compile(); 
} 

可以调用作为...

var addAssign1 = CreateMethod("Field1"); 

// equivalent to mc1.Field1 += mc2.Field1; 
addAssign1(mc1,mc2); 
0

什么你要找的是运算符重载:

例如,假设你有T A和T B, 你可以这样做:

public class T{ 
     public int Field1{get;set;} 
     public static T operator +(T c1, T c2) 
     { 

      return new T{Field1=c1.Field1+c2.Field1;} 
     } 
} 

,然后你可以做:

T a,b; //initilaize here 
T c= a+b; 

获得一个新的编译值作为a和b的总和

编辑:格式化是可怕的

+0

您如何期望添加Field1,Field2 ....等等,您是否会为每个字段定义运算符? –

+0

这是设计时代码,也就是说,您正在编写实际的代码,然后编译并运行它。问题在于询问运行时代码,在运行时从运行时数据结构创建编译代码*。 – ErikE