문제

이벤트 이름이 포함된 개체 인스턴스와 문자열 이름이 주어지면 해당 이벤트를 구독하고 해당 이벤트가 발생했을 때 작업(예: 콘솔에 쓰기)을 수행하도록 C# 이벤트를 동적으로 구독하려면 어떻게 해야 할까요?

Reflection을 사용하는 것은 불가능해 보이며 가능하다면 Reflection.Emit을 사용하지 않고 싶습니다. 현재로서는 이것이 유일한 방법인 것 같습니다.

/편집하다: 행사에 필요한 대리인의 서명을 모르겠습니다. 이것이 문제의 핵심입니다.

/편집 2: 대리자 반공변성은 좋은 계획처럼 보이지만 이 솔루션을 사용하는 데 필요한 가정을 할 수 없습니다.

도움이 되었습니까?

해결책

인수 없이 void 메서드를 모든 유형의 이벤트에 대한 이벤트 처리기로 사용하도록 식 트리를 컴파일할 수 있습니다.다른 이벤트 핸들러 유형을 수용하려면 이벤트 핸들러의 매개변수를 이벤트에 매핑해야 합니다.

 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();
     }
 }

다른 팁

완전히 일반적인 해결책은 아니지만 모든 이벤트가 void foo (Object O, T Args) 형식이라면 T가 EventArgs에서 파생 된 경우 대의원 불밀을 사용하여 도망 갈 수 있습니다.다음과 같습니다(KeyDown의 서명이 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!");
    }

Reflection을 사용하여 이벤트를 구독할 수 있습니다.

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

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

이제 여기에 당신이 해결해야 할 문제가 있을 것입니다.각 이벤트 처리기에 필요한 대리자는 서로 다른 서명을 갖습니다.이러한 메서드(아마도 Reflection.Emit를 의미함)를 동적으로 생성하려면 다른 방법을 찾아야 합니다. 또는 컴파일된 코드로 처리할 수 있도록 자체를 특정 대리자로 제한해야 합니다.

도움이 되었기를 바랍니다.

LinFu를 사용해 보세요. 런타임 시 모든 이벤트에 바인딩할 수 있는 범용 이벤트 핸들러가 있습니다.예를 들어, 동적 버튼의 Click 이벤트에 핸들러를 바인딩할 수 있는 방법은 다음과 같습니다.

// 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를 사용하면 대리자 서명에 관계없이 핸들러를 모든 이벤트에 바인딩할 수 있습니다.즐기다!

여기에서 찾을 수 있습니다:http://www.codeproject.com/KB/cs/LinFuPart3.aspx

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
}

나는 최근에 단위 테스트 이벤트를 설명하는 일련의 블로그 게시물을 작성했으며, 내가 논의하는 기술 중 하나는 동적 이벤트 구독에 대해 설명합니다.동적 측면을 위해 리플렉션과 MSIL(코드 방출)을 사용했지만 이는 모두 훌륭하게 마무리되었습니다.DynamicEvent 클래스를 사용하면 다음과 같이 이벤트를 동적으로 구독할 수 있습니다.

EventPublisher publisher = new EventPublisher();

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

내가 구현한 패턴의 기능 중 하나는 이벤트 핸들러 호출에 이벤트 이름을 삽입하여 어떤 이벤트가 발생했는지 알 수 있다는 것입니다.단위 테스트에 매우 유용합니다.

블로그 글은 이벤트 단위 테스트 기법을 설명하기 때문에 꽤 길지만, 전체 소스 코드와 테스트가 제공되며, 동적 이벤트 구독이 어떻게 구현되었는지에 대한 자세한 설명은 마지막 게시물에 자세히 설명되어 있습니다.

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

종속성 주입을 사용하면 원하는 것을 얻을 수 있습니다.예를 들어 Microsoft 복합 UI 앱 블록 당신이 설명한 것과 정확히 일치합니다

이 메소드는 메소드를 호출하는 동적 핸들러인 이벤트에 추가됩니다. OnRaised, 이벤트 매개변수를 객체 배열로 전달합니다.

void Subscribe(object source, EventInfo ev)
{
    var eventParams = ev.EventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
    var eventHandler = Expression.Lambda(ev.EventHandlerType,
        Expression.Call(
            instance: Expression.Constant(this),
            method: typeof(EventSubscriber).GetMethod(nameof(OnRaised), BindingFlags.NonPublic | BindingFlags.Instance),
            arg0: Expression.Constant(ev.Name),
            arg1: Expression.NewArrayInit(typeof(object), eventParams.Select(p => Expression.Convert(p, typeof(object))))),
        eventParams);
    ev.AddEventHandler(source, eventHandler.Compile());
}

OnRaised 다음 서명이 있습니다.

void OnRaised(string name, object[] parameters);

다음과 같은 뜻인가요?

//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 );

이는 구독을 수행하지 않지만 호출할 메소드를 제공합니다.

편집하다:

이 답변 후에 게시물이 명확해졌습니다. 유형을 모르면 제 예가 도움이 되지 않습니다.

그러나 .Net의 모든 이벤트는 기본 이벤트 패턴을 따라야 하므로 이를 따르는 한 기본 EventHandler와 함께 작동합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top