سؤال

كيف يمكنك الاشتراك ديناميكيًا في حدث C# بحيث يكون لديك مثيل كائن واسم سلسلة يحتوي على اسم الحدث، وتشترك في هذا الحدث وتفعل شيئًا (الكتابة إلى وحدة التحكم على سبيل المثال) عندما يتم إطلاق هذا الحدث؟

يبدو أن استخدام الانعكاس غير ممكن وأود تجنب الاضطرار إلى استخدام الانعكاس. انبعاث إن أمكن، حيث يبدو هذا حاليًا (بالنسبة لي) هو الطريقة الوحيدة للقيام بذلك.

/يحرر: لا أعرف توقيع المندوب المطلوب للحدث، هذا هو جوهر المشكلة

/ تحرير 2: على الرغم من أن تناقض المندوب يبدو وكأنه خطة جيدة، إلا أنني لا أستطيع القيام بالافتراض اللازم لاستخدام هذا الحل

هل كانت مفيدة؟

المحلول

يمكنك تجميع أشجار التعبير لاستخدام الأساليب الفارغة بدون أية وسائط كمعالجات الأحداث للأحداث من أي نوع.لاستيعاب أنواع معالجات الأحداث الأخرى، يجب عليك تعيين معلمات معالج الأحداث للأحداث بطريقة ما.

 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 (كائن O ، t args) ، حيث مستمدة من 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!");
    }

من الممكن الاشتراك في حدث باستخدام الانعكاس

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 Composite 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