【问题标题】:How to call method with EventHandler parameter using expression trees?如何使用表达式树调用带有 EventHandler 参数的方法?
【发布时间】:2019-11-01 10:59:00
【问题描述】:

考虑一下这段简单的代码。 如何使用表达式树来做到这一点?

ErrorsChangedEventManager.AddHandler(obj, obj.SomeHandler);

这是一个小示例,说明了我要完成的工作。 (添加对WindowBase 的引用以使其编译。)

class Program : INotifyDataErrorInfo
{
    public int Id { get; set; }
    static void Main(string[] args)
    {
        var p1 = new Program { Id = 1 };
        var p2 = new Program { Id = 2 };

        // Here is the root of the problem.
        // I need to do this INSIDE the expression from a given instance of Program.
        EventHandler<DataErrorsChangedEventArgs> handler = p1.OnError;
        var handlerConstant = Expression.Constant(handler);

        var mi = typeof(ErrorsChangedEventManager).GetMethod(nameof(ErrorsChangedEventManager.AddHandler), 
            BindingFlags.Public | BindingFlags.Static);

        var source = Expression.Parameter(typeof(INotifyDataErrorInfo), "source");
        var program = Expression.Parameter(typeof(Program), "program");

        // This will work, but the OnError method will be invoked on the wrong instance.
        // So, I need to get the expression to perform what would otherwise be easy in code...
        // E.g. AddHandler(someObject, p2.OnError);
        var call = Expression.Call(mi, source, handlerConstant);

        var expr = Expression.Lambda<Action<INotifyDataErrorInfo, Program>>(call, source, program);
        var action = expr.Compile();
        action.DynamicInvoke(p1, p2);

        p1.ErrorsChanged.Invoke(p1, new DataErrorsChangedEventArgs("Foo"));
    }

    void OnError(object sender, DataErrorsChangedEventArgs e)
    {
        if (sender is Program p)
        {
            Console.WriteLine($"OnError called for Id={Id}. Expected Id=2");
        }
    }

    public IEnumerable GetErrors(string propertyName) => Enumerable.Empty<string>();
    public bool HasErrors => false;
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}

显然,它不起作用。我需要以某种方式提供OnError 处理程序作为调用的参数

【问题讨论】:

  • 在创建Expression.Lambda 时,您从EventHandler&lt;DataErrorsChangedEventArgs&gt; 切换到DataErrorsChangedEventArgs。留在EventHandler&lt;DataErrorsChangedEventArgs&gt;,你应该没事吧?
  • 我还缺少一些东西。 lambda 的创建失败:System.ArgumentException: 'ParameterExpression of type 'System.ComponentModel.INotifyDataErrorInfo' cannot be used for delegate parameter of type 'System.Object''
  • 您的source 参数是Program 类型,因此您的Action 需要是Action&lt;Program, EventHandler&lt;DataErrorsChangedEventArgs&gt;&gt;(为了更通用,您的source 应该是typeof(INotifyDataErrorInfo),并且你的Action 应该是Action&lt;INotifyDataErrorInfo, EventHandler&lt;DataErrorsChangedEventArgs&gt;&gt;
  • 好吧,如果我可以在代码中使用p.OnError,那么我就可以构建一个 lambda。问题是为“p.OnError”构建表达式树,给定一个类型为“Program”的参数,对于任何实例......
  • 我不关注。你能举例说明你想如何使用它吗?

标签: c# lambda expression-trees


【解决方案1】:

似乎最简单的做法是创建一个 lambda,它为您创建 EventHandler&lt;DataErrorsChangedEventArgs&gt;,然后使用 Expression.Invoke 调用它:

public class Program : INotifyDataErrorInfo
{
    public int Id { get; set; }

    public static void Main()
    {
        var p1 = new Program { Id = 1 };
        var p2 = new Program { Id = 2 };

        var mi = typeof(ErrorsChangedEventManager).GetMethod(nameof(ErrorsChangedEventManager.AddHandler),
            BindingFlags.Public | BindingFlags.Static);

        var source = Expression.Parameter(typeof(INotifyDataErrorInfo), "source");
        var program = Expression.Parameter(typeof(Program), "program");

        Expression<Func<Program, EventHandler<DataErrorsChangedEventArgs>>> createDelegate = p => p.OnError;
        var createDelegateInvoke = Expression.Invoke(createDelegate, program);

        var call = Expression.Call(mi, source, createDelegateInvoke);
        var expr = Expression.Lambda<Action<INotifyDataErrorInfo, Program>>(call, source, program);
        var action = expr.Compile();

        action(p1, p2);

        p1.ErrorsChanged.Invoke(p1, new DataErrorsChangedEventArgs("Foo"));
    }

    public void OnError(object sender, DataErrorsChangedEventArgs e)
    {
        if (sender is Program p)
        {
            Console.WriteLine($"OnError called for Id={Id}. Expected Id=2");
        }
    }

    public IEnumerable GetErrors(string propertyName) => Enumerable.Empty<string>();
    public bool HasErrors => false;
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}

如果您查看createDelegate 的DebugView,您可以看到编译器已创建:

.Lambda #Lambda1<System.Func`2[Program,System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]]>(Program $p)
{
    (System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]).Call .Constant<System.Reflection.MethodInfo>(Void OnError(System.Object, System.ComponentModel.DataErrorsChangedEventArgs)).CreateDelegate(
        .Constant<System.Type>(System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]),
        $p)
}

如果你愿意,你可以自己构造这个表达式,方法是为OnError 获取MethodInfo,然后在上面调用CreateDelegate


综上所述,您可以使用 lambda 来完成所有这些操作:

Expression<Action<INotifyDataErrorInfo, Program>> test = (source, program) =>
    ErrorsChangedEventManager.AddHandler(source, program.OnError);

【讨论】:

  • 使用 Expression&lt;Func&lt;&gt;&gt; 技巧肯定会让事情变得更容易!
猜你喜欢
  • 2011-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-05
  • 2011-11-07
  • 1970-01-01
相关资源
最近更新 更多