簡體   English   中英

如何使用表達式樹調用帶有 EventHandler 參數的方法?

[英]How to call method with EventHandler parameter using expression trees?

考慮一下這段簡單的代碼。 如何使用表達式樹來做到這一點?

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處理程序作為調用的參數

似乎最簡單的做法是創建一個 lambda ,它為您創建EventHandler<DataErrorsChangedEventArgs> ,然后使用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)
}

如果需要,您可以自己構造此表達式,方法是獲取OnErrorMethodInfo ,然后在其上調用CreateDelegate


綜上所述,您只需使用 lambda 即可完成所有這些操作:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM