在Factory.Process(..)方法中,我想要获取正在Func委托的lambda表达式中使用的MyClass实例。但是,怎么样? 有人可以帮我找到办法吗。
编辑:这是一个证明我需要的人为例子。我在这种方法背后的意图是,我想跟踪(或订阅)委托定义中使用的所有MyClass对象。这样,每当更改任何MyClass对象值时,我都可以重新计算总值。请建议如何解决这个问题。
注意:表达式树在我的情况下似乎没有帮助,因为此时我无法修改我的参数类型,它限制了我的复杂函数定义的使用。
public class MyClass
{
public int Value;
public MyClass(int value)
{
Value = value;
}
}
public class TestClass
{
public void TestMethod()
{
var obj1 = new MyClass(10);
var obj2 = new MyClass(20);
Factory.Process(() => obj1.Value + obj2.Value);
}
}
public static class Factory
{
public static void Process(Func<int> function)
{
var total = function.Invoke();
// Here, apart from invoke, I want to access the all the instances of MyClass that are used in 'function'
// but how do I get to obj1 and obj2 objects through the 'function' delegate?
}
}
答案 0 :(得分:1)
首先,如果您输入的输入参数只是Func<T>
,那么它不是表达式,而只是委托的lambda语法。
如果您希望能够访问表达式并执行某些反射和/或分析,则需要将参数键入为Expression<T>
。例如:Expression<Func<int>>
。这会将您的表达式转换为表达式树。
Expression trees使您能够像数据结构一样访问表达式。完成对表达式树的分析后,可以调用yourExpression.Compile()
,这会将表达式树编译成一个委托,可以像任何其他委托(命名和匿名委托)一样调用。
例如,obj1
将以这种方式访问:
public class MyClass
{
public int Value { get; set; }
}
static void Main(string[] args)
{
var obj1 = new MyClass { Value = 1 };
var obj2 = new MyClass { Value = 2 };
Expression<Func<int>> expr = () => obj1.Value + obj2.Value;
BinaryExpression binaryExpr = (BinaryExpression)expr.Body;
MemberExpression memberExpr = (MemberExpression)binaryExpr.Left;
MemberExpression fieldExpr = (MemberExpression)memberExpr.Expression;
ConstantExpression constantExpr = (ConstantExpression)fieldExpr.Expression;
dynamic value = constantExpr.Value;
MyClass some = value.obj1;
}
不幸的是,从参数Func更改为 表达&GT;似乎在我的情况下运作良好,因为, 表达式树限制了我的函数委托定义 使用赋值运算符和语句体。
我对此的回答是,您需要一个不存在的通用解决方案,因为任何其他解决方案都可能会影响可维护性。
也许还有一个替代方案可以让你留下代表而不是表达式树:一个带有out
参数的委托,其中包含参与其中的对象集合......
由于标准BCL Func
代表没有输出参数,您可以按如下方式声明自己的Func
代理:
public delegate TResult Func<out T>(out IDictionary<string, object> objects);
...并且您的代理人应该设置所谓的out
参数:
using System;
using System.Collections.Generic;
public class Program
{
public class MyClass
{
public int Value { get; set; }
}
public delegate void Func<out T>(out IDictionary<string, object> objects);
public static void Main()
{
Func<int> someFunc = (out IDictionary<string, object> objects) =>
{
var obj1 = new MyClass { Value = 1 };
var obj2 = new MyClass { Value = 2 };
int result = obj1.Value + obj2.Value;
objects = new Dictionary<string, object> { { "obj1", obj1 }, { "obj2", obj2 } };
};
IDictionary<string, object> objectsInFunc;
someFunc(out objectsInFunc);
}
}
答案 1 :(得分:0)
如上所述,它无法完成,因为您正在尝试访问有关确切代码的信息,因此需要检查传入的IL(因为编译后实际的C#代码已经消失)。
但是,使用System.Linq.Expression
命名空间的元代码库可能会有所帮助,但前提是Func<int>
代替Expression<Func<int>>
。有了这个,你就可以走你的lambda调用所创建的expressions tree。使用Expression
代替另一个委托类型也告诉编译器创建一个表达式树,而不是实际编译代码,所以如果你传递一个直接的方法或尝试检查非代码,这将不起作用表达式树对象的方式相同。
答案 2 :(得分:0)
如果您将Process
的参数类型更改为Expression<Func<int>> expr
:
public static void Process(Expression<Func<int>> expr)
{
Func<int> function = expr.Compile();
var total = function();
Expression left = ((BinaryExpression)expr.Body).Left;
Expression leftObjExpr = ((MemberExpression)left).Expression;
Expression<Func<MyClass>> leftLambda =
Expression.Lambda<Func<MyClass>>(leftObjExpr);
Func<MyClass> leftFunc = leftLambda.Compile();
MyClass obj1 = leftFunc();
int value = obj1.Value; // ==> 10
// Same with right operand...
}
请注意,您仍然可以调用该函数;你只需要编译lambda表达式来获得一个可调用的函数。
但是,这只适用于二进制表达式。如果要解析所有类型的表达式,这会变得非常复杂。您最好使用Visitor pattern解决此问题。