2012-10-26 30 views
5

Tôi có một điều khiển WPF với tài liệu rất kém.Binding động để C# Sự kiện Sử dụng Reflection

Trong codebehind, tôi muốn phản ánh các sự kiện mà bộ điều khiển kích hoạt bằng cách sử dụng GetType().GetEVents() và thêm trình xử lý cho mỗi đơn vị chỉ in ra tên của sự kiện.

Điều này sẽ cho phép tôi xem những gì tương tác với kiểm soát thực sự đang thực hiện.

Cho đến nay tôi có:

foreach (var e in GetType().GetEvents()) 
{ 
    var name = e.Name; 
    var handler = new Action<object,object>((o1,o2) =>Console.WriteLine(name)); 

    try 
    { 
     e.AddEventHandler(
        this, 
        Delegate.CreateDelegate(
           e.EventHandlerType, 
           handler.Target, 
           handler.Method 
           )); 
    } 
    catch (Exception ex) 
    { 
     Console.WriteLine("Failed to bind to event {0}", e.Name); 
    } 
} 

Mà dường như để làm việc khi chữ ký sự kiện là (object,EventArgs) nhưng không ràng buộc khi trên một số sự kiện khác.

Có cách nào để thực hiện việc này mà không nhất thiết phải biết chữ ký của sự kiện không?

Trả lời

6

Bạn có thể sử dụng lớp System.Linq.Expressions.Expression để tạo trình xử lý động khớp với chữ ký của sự kiện - mà bạn chỉ cần thực hiện cuộc gọi đến Console.WriteLine.

Phương pháp Expression.Lambda (đã cung cấp liên kết đến tình trạng quá tải cụ thể mà bạn cần) có thể được sử dụng để tạo ra Func<> hoặc, nhiều khả năng, Action<> của loại chính xác.

Bạn phản ánh loại đại biểu của sự kiện (lấy phương thức Invoke như đã đề cập bởi @Davio) để rút ra tất cả các đối số và tạo ParameterExpression s cho mỗi người cung cấp phương thức lambda.

Dưới đây là một giải pháp hoàn chỉnh mà bạn có thể dán vào một thử nghiệm đơn vị tiêu chuẩn, tôi sẽ giải thích sau đó trong một theo dõi chỉnh sửa:

public class TestWithEvents 
{ 
    //just using random delegate signatures here 
    public event Action Handler1; 
    public event Action<int, string> Handler2; 

    public void RaiseEvents(){ 
    if(Handler1 != null) 
     Handler1(); 
    if(Handler2 != null) 
     Handler2(0, "hello world"); 
    } 
} 

public static class DynamicEventBinder 
{ 
    public static Delegate GetHandler(System.Reflection.EventInfo ev) { 
    string name = ev.Name; 
    // create an array of ParameterExpressions 
    // to pass to the Expression.Lambda method so we generate 
    // a handler method with the correct signature. 
    var parameters = ev.EventHandlerType.GetMethod("Invoke").GetParameters(). 
     Select((p, i) => Expression.Parameter(p.ParameterType, "p" + i)).ToArray(); 

    // this and the Compile() can be turned into a one-liner, I'm just 
    // splitting it here so you can see the lambda code in the Console 
    // Note that we use the Event's type for the lambda, so it's tightly bound 
    // to that event. 
    var lambda = Expression.Lambda(ev.EventHandlerType, 
     Expression.Call(typeof(Console).GetMethod(
     "WriteLine", 
     BindingFlags.Public | BindingFlags.Static, 
     null, 
     new[] { typeof(string) }, 
     null), Expression.Constant(name + " was fired!")), parameters); 

    //spit the lambda out (for bragging rights) 
    Console.WriteLine(
     "Compiling dynamic lambda {0} for event \"{1}\"", lambda, name); 
    return lambda.Compile(); 
    } 

    //note - an unsubscribe might be handy - which would mean 
    //caching all the events that were subscribed for this object 
    //and the handler. Probably makes more sense to turn this type 
    //into an instance type that binds to a single object... 
    public static void SubscribeAllEvents(object o){ 
    foreach(var e in o.GetType().GetEvents()) 
    { 
     e.AddEventHandler(o, GetHandler(e)); 
    } 
    } 
} 

[TestMethod] 
public void TestSubscribe() 
{ 
    TestWithEvents testObj = new TestWithEvents(); 
    DynamicEventBinder.SubscribeAllEvents(testObj); 
    Console.WriteLine("Raising events..."); 
    testObj.RaiseEvents(); 
    //check the console output 
} 

Bản đề cương - chúng ta bắt đầu với một loại mà có một số sự kiện (Tôi đang sử dụng Action nhưng nó sẽ làm việc với bất cứ điều gì), và có một phương pháp mà chúng ta có thể sử dụng để kiểm tra tất cả những sự kiện có thuê bao.

sau đó đến lớp DynamicEventBinder, có hai phương pháp: GetHandler - để có trình xử lý cho một sự kiện cụ thể cho một loại cụ thể; và liên kết tất cả các sự kiện đó cho một trường hợp cụ thể của loại đó - chỉ đơn giản là lặp lại tất cả các sự kiện, gọi AddEventHandler cho mỗi sự kiện, gọi GetHandler để nhận trình xử lý.

Phương pháp GetHandler là nơi thịt và xương - và thực hiện chính xác như tôi đề xuất trong đường viền.

Loại đại biểu có thành viên Invoke được biên dịch thành bộ biên dịch phản ánh chữ ký của bất kỳ trình xử lý nào mà nó có thể bị ràng buộc. Vì vậy, chúng tôi phản ánh phương pháp đó và nhận được bất kỳ tham số nào có, tạo ra các trường hợp LINQ ParameterExpression cho mỗi phương thức. Đặt tên cho tham số là một yếu tố độc đáo, đó là loại ở đây quan trọng.

Sau đó, chúng tôi xây dựng một dòng đơn lambda, mà cơ thể về cơ bản là:

Console.WriteLine("[event_name] was fired!"); 

(Lưu ý ở đây là tên của sự kiện được kéo vào mã năng động và tích hợp vào một chuỗi liên tục như xa như mã là có liên quan)

Khi chúng ta xây dựng lambda, chúng tôi cũng nói với Expression.Lambda phương pháp loại đại biểu chúng tôi dự định xây dựng (ràng buộc trực tiếp đến loại đại biểu về sự kiện này), và bằng cách truyền ParameterExpression mảng mà chúng ta tạo ra trước , nó sẽ tạo ra một phương thức có t hat nhiều thông số. Chúng tôi sử dụng phương pháp Compile để thực sự biên dịch mã động, cung cấp cho chúng tôi Delegate mà sau đó chúng tôi có thể sử dụng làm đối số cho AddEventHandler.

Tôi chân thành hy vọng điều này giải thích những gì chúng tôi đã làm - nếu bạn không làm việc với Biểu thức và mã động trước khi nó có thể là nội dung uốn cong tâm trí. Thật vậy, một số người tôi làm việc chỉ đơn giản là gọi voodoo này.

+0

Điều này nghe có vẻ thú vị nhưng tôi đang đấu tranh để thực hiện! – Nick

+0

Tôi sẽ đặt cùng một thử nghiệm với loại + và sự kiện để bạn sử dụng ... có thể mất nửa giờ ... –

+0

Tôi đã có một phần mà tôi gọi AddHandler trên đối tượng EventInfo và tôi nhận được: * "Đối tượng của loại 'System.Action'2 [System.Object, System.Windows.Input.MouseButtonEventArgs]' không thể được chuyển đổi thành kiểu 'System.Windows.Input.MouseButtonEventHandler'." * – Nick

Các vấn đề liên quan