问题 如何从lambda表达式获取引用实例的实例


我有这个lambda表达式 Expression<Func<bool>> commandToExecute 

然后我用一个方法传递一个类的实例:

_commandExecuter.ProcessCommand (() => aClass.Method())

我如何获得实例 aClass 在...内 ProcessCommand 方法?

我想执行此类的一些addiontal方法或获取一些属性值。

这可能吗?

编辑: 我现在已经编写了一个简单的静态帮助器方法来获取实例:

private static object GetReferredProviderInstance(Expression body)
{
    var methodCallExpression = body as MethodCallExpression;
    if (methodCallExpression != null)
    {
        var constantExpression = methodCallExpression.Object as ConstantExpression;
        if (constantExpression != null) return constantExpression.Value;
    }
    return null;
}

方法调用看起来像这样......

Expression body = commandToExecute.Body; // this is the method parameter Expression<Func<bool>> commandToExecute
var referredProviderInstance = GetReferredProviderInstance(body);

这里的问题是,ConstantExpression的转换结果是 Null。所以 constantExpression 永远是空的。

有任何想法吗?

编辑2 我解决了这个问题......

private static object GetReferredProviderInstance(Expression body)
{
    var methodCallExpression = body as MethodCallExpression;
    if (methodCallExpression != null)
    {
        var memberExpression = methodCallExpression.Object as MemberExpression;
        if (memberExpression != null)
        {
            var constantExpression = memberExpression.Expression as ConstantExpression;
            if (constantExpression != null) return constantExpression.Value;
        }
    }
    return null;
}

但是这里出现了一个新问题。我只获得了我的提供者的参考实例所在的Windows窗体的实例。

我如何得到 真实的对象 (aClass)lambda表达式?


11760
2017-08-31 08:53


起源



答案:


这实际上是可能的,但这取决于您传递给此方法的内容。假设您有一个方案,您传递您所在的类的实例方法 ProcessCommand

public class TestClass
{
    public void TestMethod()
    {
        ProcessCommand(() => MethodToCall());
    }
    public bool MethodToCall() { return true; }
    void ProcessCommand(Expression<Func<bool>> expression) { ... }
}

然后您可以使用以下内容 ProcessCommand 方法。这只能起作用 MethodToCall 在这个实例上被调用。

void ProcessCommand(Expression<Func<bool>> expression)
{
    var lambda = (LambdaExpression) expression;
    var methodCall = (MethodCallExpression) lambda.Body;
    var constant = (ConstantExpression) methodCall.Object;
    var myObject = constant.Value;
}

更复杂的情况如下:

public class CallingClass
{
    public void TestMethod()
    {
        var calledClass = new CalledClass();
        ProcessCommand(() => calledClass.MethodToCall());
    }
    void ProcessCommand(Expression<Func<bool>> expression) { ... }
}
public class CalledClass
{
    public bool MethodToCall() { return true; }
}

我们调用的方法现在在另一个类中,并且不在此实例上调用,而是在实例上调用 CalledClass 叫 calledClass。但编译器如何通过 calledClass 变量进入lambda表达式?没有什么可以定义一个领域 calledClass 那个方法 MethodToCall 可以被召唤。

编译器通过生成一个带有名称的一个字段的内部类来解决这个问题 calledClass。结果呢 ProcessCommand 方法现在成为这个:

public void ProcessCommand(Expression<Func<bool>> expression)
{
    // The expression is a lambda expression with a method call body.
    var lambda = (LambdaExpression) expression;
    var methodCall = (MethodCallExpression) lambda.Body;
    // The method is called on a member of some instance.
    var member = (MemberExpression) methodCall.Object;
    // The member expression contains an instance of the anonymous class that
    // defines the member...
    var constant = (ConstantExpression) member.Expression;
    var anonymousClassInstance = constant.Value;
    // ...and the member itself.
    var calledClassField = (FieldInfo) member.Member;
    // With an instance of the anonymous class and the field, we can get its value.
    var calledClass =
        (CalledClass) calledClassField.GetValue(anonymousClassInstance);
}

稍微复杂一点,因为编译器必须生成一个匿名内部类。


12
2017-08-31 09:21



太好了,谢谢! - Rookian
如果能回答你的问题,你能否将此标记为答案? - Ronald Wildenberg
我已经编辑了我的问题......请复习 - Rookian
事实证明这比我最初想的要复杂一点。我用太简单的例子测试过。我会更新我的答案,以显示它毕竟是可能的。 - Ronald Wildenberg
谢谢,这很好:) - Rookian


答案:


这实际上是可能的,但这取决于您传递给此方法的内容。假设您有一个方案,您传递您所在的类的实例方法 ProcessCommand

public class TestClass
{
    public void TestMethod()
    {
        ProcessCommand(() => MethodToCall());
    }
    public bool MethodToCall() { return true; }
    void ProcessCommand(Expression<Func<bool>> expression) { ... }
}

然后您可以使用以下内容 ProcessCommand 方法。这只能起作用 MethodToCall 在这个实例上被调用。

void ProcessCommand(Expression<Func<bool>> expression)
{
    var lambda = (LambdaExpression) expression;
    var methodCall = (MethodCallExpression) lambda.Body;
    var constant = (ConstantExpression) methodCall.Object;
    var myObject = constant.Value;
}

更复杂的情况如下:

public class CallingClass
{
    public void TestMethod()
    {
        var calledClass = new CalledClass();
        ProcessCommand(() => calledClass.MethodToCall());
    }
    void ProcessCommand(Expression<Func<bool>> expression) { ... }
}
public class CalledClass
{
    public bool MethodToCall() { return true; }
}

我们调用的方法现在在另一个类中,并且不在此实例上调用,而是在实例上调用 CalledClass 叫 calledClass。但编译器如何通过 calledClass 变量进入lambda表达式?没有什么可以定义一个领域 calledClass 那个方法 MethodToCall 可以被召唤。

编译器通过生成一个带有名称的一个字段的内部类来解决这个问题 calledClass。结果呢 ProcessCommand 方法现在成为这个:

public void ProcessCommand(Expression<Func<bool>> expression)
{
    // The expression is a lambda expression with a method call body.
    var lambda = (LambdaExpression) expression;
    var methodCall = (MethodCallExpression) lambda.Body;
    // The method is called on a member of some instance.
    var member = (MemberExpression) methodCall.Object;
    // The member expression contains an instance of the anonymous class that
    // defines the member...
    var constant = (ConstantExpression) member.Expression;
    var anonymousClassInstance = constant.Value;
    // ...and the member itself.
    var calledClassField = (FieldInfo) member.Member;
    // With an instance of the anonymous class and the field, we can get its value.
    var calledClass =
        (CalledClass) calledClassField.GetValue(anonymousClassInstance);
}

稍微复杂一点,因为编译器必须生成一个匿名内部类。


12
2017-08-31 09:21



太好了,谢谢! - Rookian
如果能回答你的问题,你能否将此标记为答案? - Ronald Wildenberg
我已经编辑了我的问题......请复习 - Rookian
事实证明这比我最初想的要复杂一点。我用太简单的例子测试过。我会更新我的答案,以显示它毕竟是可能的。 - Ronald Wildenberg
谢谢,这很好:) - Rookian


它不可能“开箱即用”,你可以用反射来破解某些东西, 但这不可取,它会非常倒退。
编辑: 实际上根据罗纳德可能,但仍然相当倒退。这样的隐藏副作用使代码难以阅读和维护。

相反,您的ProcessCommand应该采取整体 aClass 物体或更优选地 IMyCommand 界面与 .Method() 以及其他方法和属性 ProcessCommand 需要。然后 aClass.GetType() 类型应该实现 IMyCommand


2
2017-08-31 09:06