2008-09-05 40 views
30

Làm thế nào bạn tự động đăng ký một sự kiện C# để cho một cá thể đối tượng và tên chuỗi chứa tên của sự kiện, bạn đăng ký sự kiện đó và làm điều gì đó (ghi vào bàn điều khiển chẳng hạn) khi sự kiện đó đã bị sa thải?C# Đăng ký sự kiện động

Có vẻ như việc sử dụng Phản ánh điều này là không thể và tôi muốn tránh phải sử dụng Reflection.Emit nếu có thể, vì hiện tại (với tôi) dường như là cách duy nhất để thực hiện.

/EDIT: Tôi không biết chữ ký của người đại biểu cần thiết cho sự kiện này, đây là cốt lõi của vấn đề

/EDIT 2: Mặc dù đại biểu contravariance có vẻ như một kế hoạch tốt, tôi không thể đưa ra giả định cần thiết để sử dụng giải pháp này

Trả lời

28

Bạn có thể biên dịch cây biểu thức để sử dụng các phương thức trống mà không có bất kỳ đối số nào là trình xử lý sự kiện cho bất kỳ loại sự kiện nào. Để chứa các kiểu trình xử lý sự kiện khác, bạn phải ánh xạ các tham số của trình xử lý sự kiện cho các sự kiện bằng cách nào đó.

using System; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 

class ExampleEventArgs : EventArgs 
{ 
    public int IntArg {get; set;} 
} 

class EventRaiser 
{ 
    public event EventHandler SomethingHappened; 
    public event EventHandler<ExampleEventArgs> SomethingHappenedWithArg; 

    public void RaiseEvents() 
    { 
     if (SomethingHappened!=null) SomethingHappened(this, EventArgs.Empty); 

     if (SomethingHappenedWithArg!=null) 
     { 
      SomethingHappenedWithArg(this, new ExampleEventArgs{IntArg = 5}); 
     } 
    } 
} 

class Handler 
{ 
    public void HandleEvent() { Console.WriteLine("Handler.HandleEvent() called.");} 
    public void HandleEventWithArg(int arg) { Console.WriteLine("Arg: {0}",arg); } 
} 

static class EventProxy 
{ 
    //void delegates with no parameters 
    static public Delegate Create(EventInfo evt, Action d) 
    { 
     var handlerType = evt.EventHandlerType; 
     var eventParams = handlerType.GetMethod("Invoke").GetParameters(); 

     //lambda: (object x0, EventArgs x1) => d() 
     var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")); 
     var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke")); 
     var lambda = Expression.Lambda(body,parameters.ToArray()); 
     return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false); 
    } 

    //void delegate with one parameter 
    static public Delegate Create<T>(EventInfo evt, Action<T> d) 
    { 
     var handlerType = evt.EventHandlerType; 
     var eventParams = handlerType.GetMethod("Invoke").GetParameters(); 

     //lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg) 
     var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray(); 
     var arg = getArgExpression(parameters[1], typeof(T)); 
     var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg); 
     var lambda = Expression.Lambda(body,parameters); 
     return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false); 
    } 

    //returns an expression that represents an argument to be passed to the delegate 
    static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType) 
    { 
     if (eventArgs.Type==typeof(ExampleEventArgs) && handlerArgType==typeof(int)) 
     { 
      //"x1.IntArg" 
      var memberInfo = eventArgs.Type.GetMember("IntArg")[0]; 
      return Expression.MakeMemberAccess(eventArgs,memberInfo); 
     } 

     throw new NotSupportedException(eventArgs+"->"+handlerArgType); 
    } 
} 


static class Test 
{ 
    public static void Main() 
    { 
     var raiser = new EventRaiser(); 
     var handler = new Handler(); 

     //void delegate with no parameters 
     string eventName = "SomethingHappened"; 
     var eventinfo = raiser.GetType().GetEvent(eventName); 
     eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,handler.HandleEvent)); 

     //void delegate with one parameter 
     string eventName2 = "SomethingHappenedWithArg"; 
     var eventInfo2 = raiser.GetType().GetEvent(eventName2); 
     eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,handler.HandleEventWithArg)); 

     //or even just: 
     eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,()=>Console.WriteLine("!"))); 
     eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,i=>Console.WriteLine(i+"!"))); 

     raiser.RaiseEvents(); 
    } 
} 
+0

Địa ngục, cây biểu hiện thật tuyệt. Tôi đã viết mã tương tự một lần qua Reflection.Emit. Thật là một nỗi đau. –

+0

Đoạn mã tuyệt vời. Bạn có thể cho thấy làm thế nào để thay đổi nó để hỗ trợ các đối số? Tôi đã thay đổi nó để có được phương thức với các đối số, nhưng tôi nhận được "biến 'x' của kiểu 'System.String' được tham chiếu từ phạm vi '', nhưng nó không được xác định" khi tôi cố gắng tạo đại biểu. Cảm ơn –

+0

Xong — Tôi đã thêm một ví dụ khác. –

0

Điều bạn muốn có thể đạt được bằng cách sử dụng tính năng tiêm phụ thuộc. Ví dụ Microsoft Composite UI app block thực hiện chính xác những gì bạn mô tả

2

Có thể đăng ký vào một sự kiện sử dụng Reflection

var o = new SomeObjectWithEvent; 
o.GetType().GetEvent("SomeEvent").AddEventHandler(...); 

http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx

Bây giờ đây sẽ là những vấn đề mà bạn đang phải giải quyết . Các đại biểu cần thiết cho mỗi trình xử lý sự kiện sẽ có các chữ ký khác nhau. Bạn sẽ phải tìm cách tạo các phương thức này một cách năng động, có nghĩa là Reflection.Emit, hoặc bạn sẽ phải giới hạn bản thân của mình với một đại biểu nào đó để bạn có thể xử lý nó bằng mã được biên dịch.

Hy vọng điều này sẽ hữu ích.

-1

Bạn có nghĩa là một cái gì đó như:

//reflect out the method to fire as a delegate 
EventHandler eventDelegate = 
    (EventHandler) Delegate.CreateDelegate(
     typeof(EventHandler), //type of event delegate 
     objectWithEventSubscriber, //instance of the object with the matching method 
     eventSubscriberMethodName, //the name of the method 
     true); 

này không làm đăng ký, nhưng sẽ cung cấp cho các phương pháp để gọi.

Chỉnh sửa:

Bài đăng được làm rõ sau câu trả lời này, ví dụ của tôi sẽ không giúp ích nếu bạn không biết loại.

Tuy nhiên tất cả các sự kiện trong. Net phải tuân theo mẫu sự kiện mặc định, miễn là bạn đã theo dõi nó, thao tác này sẽ hoạt động với EventHandler cơ bản.

1
public TestForm() 
{ 
    Button b = new Button(); 

    this.Controls.Add(b); 

    MethodInfo method = typeof(TestForm).GetMethod("Clickbutton", 
    BindingFlags.NonPublic | BindingFlags.Instance); 
    Type type = typeof(EventHandler); 

    Delegate handler = Delegate.CreateDelegate(type, this, method); 

    EventInfo eventInfo = cbo.GetType().GetEvent("Click"); 

    eventInfo.AddEventHandler(b, handler); 

} 

void Clickbutton(object sender, System.EventArgs e) 
{ 
    // Code here 
} 
8

Nó không phải là một giải pháp hoàn toàn nói chung, nhưng nếu tất cả các sự kiện của bạn có dạng void (o đối tượng, args T) Foo, trong đó T xuất phát từ EventArgs, sau đó bạn có thể sử dụng đại biểu contravariance để nhận được ngay với nó. Như thế này (trong đó có chữ ký của KeyDown không giống như của Click):

public Form1() 
    { 
     Button b = new Button(); 
     TextBox tb = new TextBox(); 

     this.Controls.Add(b); 
     this.Controls.Add(tb); 
     WireUp(b, "Click", "Clickbutton"); 
     WireUp(tb, "KeyDown", "Clickbutton"); 
    } 

    void WireUp(object o, string eventname, string methodname) 
    { 
     EventInfo ei = o.GetType().GetEvent(eventname); 

     MethodInfo mi = this.GetType().GetMethod(methodname, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); 

     Delegate del = Delegate.CreateDelegate(ei.EventHandlerType, this, mi); 

     ei.AddEventHandler(o, del); 

    } 
    void Clickbutton(object sender, System.EventArgs e) 
    { 
     MessageBox.Show("hello!"); 
    } 
2

Hãy thử LinFu - nó có một xử lý sự kiện phổ biến cho phép bạn liên kết với bất kỳ sự kiện khi chạy.Ví dụ, đây là bạn bạn có thể gắn một handler cho sự kiện Click của nút động:

 
// Note: The CustomDelegate signature is defined as: 
// public delegate object CustomDelegate(params object[] args); 
CustomDelegate handler = delegate 
         { 
          Console.WriteLine("Button Clicked!"); 
          return null; 
         }; 

Button myButton = new Button(); 
// Connect the handler to the event 
EventBinder.BindToEvent("Click", myButton, handler); 

LinFu cho phép bạn liên kết xử lý của bạn để mọi trường hợp, không phân biệt chữ ký đại biểu. Thưởng thức!

Bạn có thể tìm thấy nó ở đây: http://www.codeproject.com/KB/cs/LinFuPart3.aspx

1

Gần đây tôi đã viết một loạt bài đăng trên blog mô tả các sự kiện kiểm tra đơn vị, và một trong những kỹ thuật tôi thảo luận về mô tả sự kiện thuê bao động. Tôi đã sử dụng sự phản chiếu và MSIL (mã phát ra) cho các khía cạnh năng động, nhưng điều này là tất cả được gói gọn. Sử dụng lớp DynamicEvent, các sự kiện có thể được đăng ký tự động như sau:

EventPublisher publisher = new EventPublisher(); 

foreach (EventInfo eventInfo in publisher.GetType().GetEvents()) 
{ 
    DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) => 
    { 
     Console.WriteLine("Event raised: " + eventName); 
    }); 
} 

Một trong những đặc điểm của mô hình tôi thực hiện là nó sẽ tiêm nhiễm vào tên sự kiện vào cuộc gọi để xử lý sự kiện, do đó bạn biết được sự kiện có được nuôi dưỡng. Rất hữu ích cho thử nghiệm đơn vị.

Bài viết trên blog khá dài vì nó mô tả kỹ thuật thử nghiệm đơn vị sự kiện, nhưng mã nguồn đầy đủ và các bài kiểm tra được cung cấp và mô tả chi tiết về cách đăng ký sự kiện động được thực hiện chi tiết trong bài đăng cuối cùng.

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

+0

404 trên liên kết blog của bạn có –

+0

@Robert Chúc mừng cho bình luận, chúng tôi đã có một số cúp ngày hôm qua sau khi vá máy chủ web của chúng tôi. –

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