【问题标题】:Making a lambda expression to call a method from a generic class制作 lambda 表达式以调用泛型类中的方法
【发布时间】:2017-05-21 15:07:17
【问题描述】:

我有这些简单的界面:

public interface IQuery<TResult> { }

public interface IQueryHandler<in TQuery, out TResult> 
    where TQuery : IQuery<TResult> {
    TResult Handle(TQuery query);
}

它们有一些实现。我正在尝试创建一个表达式树以在指定的处理程序上调用 Handle 方法。我的意思是:

var query = new MyQuery(); // which MyQuery implements IQuery<int>
object handler = _someServiceProvider
    .Get<IQueryHandler<MyQuery, int>>();

另外,还有一个MyQueryHandler

public class MyQueryHandler : IQueryHandler<MyQuery, int> {
    public int Handle(MyQuery query) { return 20; }
}

现在,我正在尝试创建一个 Func&lt;object, MyQuery, int&gt; 来像这样调用:

var func = GetMethod<MyQuery, int>(handler);
var result = func(handler, query);

这是我的GetMethod 实现:

    private Func<object, TQuery, TResult> GetMethod<TQuery, TResult>(object obj)
        where TQuery : IQuery<TResult> {

        var methodInfo = obj.GetType().GetMethod(nameof(IQueryHandler<TQuery, TResult>.Handle));

        var insExp = Expression.Parameter(typeof(object), "ins");

        var inputExp = Expression.Parameter(typeof(TQuery), "query");

        var instanceExp = Expression.Variable(obj.GetType(), "instance");

        var assExp = Expression.Assign(instanceExp, Expression.Convert(insExp, obj.GetType()));

        var castExp = Expression.Convert(inputExp, methodInfo.GetParameters()[0].ParameterType);

        var callExp = Expression.Call(instanceExp, methodInfo, castExp);

        var blockExp = Expression.Block(new Expression[] {
            insExp,
            inputExp,
            instanceExp,
            assExp,
            castExp,
            callExp
        });

        var func =
            Expression.Lambda<Func<object, TQuery, TResult>>(
                blockExp,
                insExp,
                inputExp).Compile();
        return func;
    }

但是,当我尝试编译 Lambda 时,我得到了这个错误:

“System.InvalidOperationException”类型的异常发生在 System.Core.dll 但未在用户代码中处理

附加信息:变量“实例”类型 'Namespace.MyQueryHandler' 从范围''引用,但它不是 定义

我哪里错了?我错过了什么?你有什么主意吗?提前致谢。

【问题讨论】:

  • 为什么需要将表达式树合并到这个?似乎过于复杂

标签: c# c#-4.0 lambda expression-trees func


【解决方案1】:

据我所知,您正在尝试编写此函数:

TResult f(object ins, TQuery query)
{
    var instance = (MyQueryHandler)ins;
    return instance.Handle(query);
}

要使用表达式树来做到这一点,您必须在 Expression.Block 中声明变量,然后只指定上面的两个语句,而不是所有子表达式:

var blockExp = Expression.Block(new[] { instanceExp }, new Expression[] {
    assExp,
    callExp
});

但更简单的选择是编写以下函数:

TResult f(object ins, TQuery query)
{
    return ((MyQueryHandler)ins).Handle(query);
}

看起来像这样:

var callExp = Expression.Call(
    Expression.Convert(insExp, obj.GetType()), methodInfo, castExp);

var func =
    Expression.Lambda<Func<object, TQuery, TResult>>(
        callExp,
        insExp,
        inputExp).Compile();

【讨论】:

  • 非常感谢。我明白了。但问题是:这里的Dump() 是什么,它有什么作用?另外,这是一个红色代码):
  • @Javad_Amiry Dump 可能是因为 svick 使用 LinqPad 进行调试。只需将其删除。
  • @Javad_Amiry David_G 是对的,我在发帖前忘记删除了。
【解决方案2】:

不清楚你想用GetMethod 中的表达式做什么,所以我不会使用它并完全从头开始编写。

如果您想将处理程序和查询都传递给您的方法 - 您不需要将任何实例传递给 GetMethod

private static Func<object, TQuery, TResult> GetMethod<TQuery, TResult>()
    where TQuery : IQuery<TResult> {
    // "query" paramter
    var query = Expression.Parameter(typeof(TQuery), "query");
    // "handler" parameter
    var handler = Expression.Parameter(typeof(object), "handler");
    // convert your "object" parameter to handler type (not type safe of course)
    // ((IQueryHandler<TQuery, TResult>) handler).Handle(query)
    var body = Expression.Call(Expression.Convert(handler, typeof(IQueryHandler<TQuery, TResult>)), "Handle", new Type[0], query);
    //(handler, query) => ((IQueryHandler<TQuery, TResult>) handler).Handle(query);
    return Expression.Lambda<Func<object, TQuery, TResult>>(body, handler, query).Compile();
}

object handler = new MyQueryHandler();
var func = GetMethod<MyQuery, int>();
var result = func(handler, query);

如果您这样做handler 实例传递给GetMethod - 您无需稍后再将同一个实例传递给您创建的func - 您可以像这样重用同一个实例(当然,假设这适合您的情况):

private static Func<TQuery, TResult> GetMethod<TQuery, TResult>(object obj)
    where TQuery : IQuery<TResult> {
    // parameter
    var query = Expression.Parameter(typeof(TQuery), "query");
    // note Expression.Constant here - you use the same instance for every call
    var body = Expression.Call(Expression.Constant(obj), "Handle", new Type[0], query);
    return Expression.Lambda<Func<TQuery, TResult>>(body, query).Compile();
}

并使用它:

var query = new MyQuery(); // which MyQuery implements IQuery<int>
object handler = new MyQueryHandler();
var func = GetMethod<MyQuery, int>(handler);
var result = func(query);

【讨论】:

  • 谢谢 :) 我用了 svick 的回答。
  • 很好,但请注意,您不需要将处理程序的实例传递给方法只是为了获取对Handle 的引用,因此您的GetMethod 可以不带参数(感觉更好不要像我一样传递无用的参数)。
  • 同意。我将参数从instance 更改为obj.GetType()。因为,您需要知道当前实例的类型是什么,并且不能使用typeof(IQueryHandler&lt;TQuery, TResult&gt;)。但再次感谢。干杯。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-20
  • 2014-04-30
  • 2021-02-12
  • 1970-01-01
相关资源
最近更新 更多