C# - 如何访问Func <int> delegate </int>中使用的特定类型的实例

时间:2014-11-03 20:45:50

标签: c# reflection delegates func

在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?
    }
}

3 个答案:

答案 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;
}

更新

OP在一些评论中表示:

  

不幸的是,从参数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解决此问题。