c# - snippet - title tag länge




Wie kann man mit einem Lambda einen neuen EventHandler erstellen? (2)

Es scheint, als müsste ich alles haben, was ich hier brauche, aber die Einzelheiten, es zu machen, machen mich verrückt.

Ich habe eine statische Hilfsmethode, die ein Web-Service-Client-Objekt übernimmt, eine designierte EventInfo daraus extrahiert und dem Ereignis einige Handler hinzufügen soll, im Wesentlichen Callbacks für den Abschluss des Web-Service-Aufrufs. Das Problem besteht darin, dass jedes Ereignis im Wesentlichen eine eigene Überladung aufweist (der von WCF generierte Code stellt für jede Methode und jedes entsprechende Ereignis ein eindeutiges SomeMethodCompletedEventArgs ). Ich kann nicht herausfinden, wie dies geschehen kann.

Ich habe zwei Handler, die ich anhängen möchte, und der erste ist nur eine Lambda-Funktion:

(obj, args) => task.Complete()

Also, was ich tun möchte, ist nur so einfach:

eventInfo.AddEventHandler(client, new EventHandler((obj, args) => task.Complete()));

Dies erzeugt jedoch eine Laufzeit InvalidCastException, da die EventInfo einen EventHandler<SomeMethodCompletedEventArgs> und keinen einfachen EventHandler . Ich glaube, das bedeutet, dass ich den EventHandler Delegaten mit eventInfo.EventHandlerType irgendwie dynamisch erstellen EventHandler , aber ich habe nicht herausgefunden, das mit der Lambda-Funktion zu kombinieren, oder sonst einen Empfänger machen, der sich nicht wirklich dafür interessiert, welcher besondere Geschmack von EventArgs ist benutzt.

Die einzige Problemumgehung, die ich gefunden habe, besteht darin, ein generisches Vorlagenargument zu erstellen, mit dem der bestimmte Ereignisargumenttyp übergeben wird. Dies ermöglicht mir, zu gehen:

eventInfo.AddEventHandler(client, new EventHandler<E>(...));

Wo E ist der fragliche Parameter. Dies ist jedoch offensichtlich klobig, und es scheint einfach falsch zu sein, dies zu übergeben, wenn die extrahierte eventInfo uns alles sagen sollte, was wir wissen müssen.

Es ist erwähnenswert, dass ich ein leicht eingeschränktes PCL-Framework für Xamarin verwende, das offensichtlich nicht die statische Methode Delegate.CreateDelegate() , die ich in verwandten Problemen erwähnt habe. Ich habe jedoch Zugang zu Activator , der die meisten der gleichen Basen abdecken sollte.


Es stellt sich heraus, dass dies nicht so schwierig ist, aber erfordert etwas Code mit ein wenig Nachdenken.

Die Grundidee besteht darin, den Handler in eine generische Klasse zu schreiben, die durch den Ereignistyp parametrisiert ist

HandlerFor<T> : IDisposable where T : EventArgs. 

Diese Klasse verfügt dann über eine private Elementfunktion, die der erforderlichen Signatur des Ereignishandlers entspricht:

void Handle(object sender, T eventArgs) 

dass es sich auf dem Bau registrieren wird, sich auf der Beseitigung abmelden wird und dass es die gegebene Action aufrufen wird, die ihm in seinem Erbauer jedesmal zur Verfügung gestellt wird, wenn das Ereignis eintritt.

Um die Implementierungsdetails auszublenden und nur ein IDisposable als Handle für den Lebenszyklusbereich des kontrollierten Ereignishandlers und die IDisposable aufzudecken, habe ich dies in eine EventHandlerRegistry Klasse EventHandlerRegistry . Dadurch können bei Bedarf auch zukünftige Verbesserungen hinzugefügt werden, z. B. das HandleFor eines Factory-Delegaten statt des wiederholten Aufrufs von Activator.CreateInstance() zum Erstellen der HandleFor Instanzen.

Es sieht aus wie das:

public class EventHandlerRegistry : IDisposable
{
    private ConcurrentDictionary<Type, List<IDisposable>> _registrations;

    public EventHandlerRegistry()
    {
        _registrations = new ConcurrentDictionary<Type, List<IDisposable>>();
    }

    public void RegisterHandlerFor(object target, EventInfo evtInfo, Action eventHandler)
    {
        var evtType = evtInfo.EventHandlerType.GetGenericArguments()[0];
        _registrations.AddOrUpdate(
            evtType,
            (t) => new List<IDisposable>() { ConstructHandler(target, evtType, evtInfo, eventHandler) },
            (t, l) => { l.Add(ConstructHandler(target, evtType, evtInfo, eventHandler)); return l; });
    }

    public IDisposable CreateUnregisteredHandlerFor(object target, EventInfo evtInfo, Action eventHandler)
    {
        var evtType = evtInfo.EventHandlerType.GetGenericArguments()[0];
        return ConstructHandler(target, evtType, evtInfo, eventHandler);
    }

    public void Dispose()
    {
        var regs = Interlocked.Exchange(ref _registrations, null);
        if (regs != null)
        {
            foreach (var reg in regs.SelectMany(r => r.Value))
                reg.Dispose();
        }
    }

    private IDisposable ConstructHandler(object target, Type evtType, EventInfo evtInfo, Action eventHandler)
    {
        var handlerType = typeof(HandlerFor<>).MakeGenericType(evtType);
        return Activator.CreateInstance(handlerType, target, evtInfo, eventHandler) as IDisposable;
    }

    private class HandlerFor<T> : IDisposable where T : EventArgs
    {
        private readonly Action _eventAction;
        private readonly EventInfo _evtInfo;
        private readonly object _target;
        private EventHandler<T> _registeredHandler;

        public HandlerFor(object target, EventInfo evtInfo, Action eventAction)
        {
            _eventAction = eventAction;
            _evtInfo = evtInfo;
            _target = target;
            _registeredHandler = new EventHandler<T>(this.Handle);
            _evtInfo.AddEventHandler(target, _registeredHandler);
        }

        public void Unregister()
        {
            var registered = Interlocked.Exchange(ref _registeredHandler, null);
            if (registered != null)
                // Unregistration is awkward: 
                // doing `RemoveEventHandler(_target, registered);` won't work.
                _evtInfo.RemoveEventHandler(_target, new EventHandler<T>(this.Handle));
        }

        private void Handle(object sender, T EventArgs)
        {
            if (_eventAction != null)
                _eventAction();
        }

        public void Dispose()
        {
            Unregister();
        }
    }
}

Es unterstützt das saubere Hinzufügen und Entfernen von Event-Handlern auf ziemlich transparente Weise. Hinweis: Dies implementiert IDisposable noch nicht in der empfohlenen Weise. Sie müssen Finalizer und Dispose(bool isFinalizing) selbst hinzufügen.

Dies zeigt ein Beispiel seiner Verwendung:

public class MyArgs1 : EventArgs
{
    public string Value1;
}

public class MyEventSource
{
    public event EventHandler<MyArgs1> Args1Event;

    public EventInfo GetEventInfo()
    {
        return this.GetType().GetEvent("Args1Event");
    }

    public void FireOne()
    {
        if (Args1Event != null)
            Args1Event(this, new MyArgs1() { Value1 = "Bla " });
    }
}

class Program
{
    public static void Main(params string[] args)
    {
        var myEventSource = new MyEventSource();
        using (var handlerRegistry = new EventHandlerRegistry())
        {
            handlerRegistry.RegisterHandlerFor(
                myEventSource,
                myEventSource.GetEventInfo(),
                () => Console.WriteLine("Yo there's some kinda event goin on"));
            handlerRegistry.RegisterHandlerFor(
                myEventSource,
                myEventSource.GetEventInfo(),
                () => Console.WriteLine("Yeah dawg let's check it out"));

            myEventSource.FireOne();
        }
        myEventSource.FireOne();
    }
}

Wenn es ausgeführt wird, gibt es die folgende Ausgabe:


In dem von Ihnen bereitgestellten Beispiel sollten Sie nur den expliziten Delegate-Konstruktor entfernen können:

eventInfo.AddEventHandler(client, (obj, args) => task.Complete());

Wenn C # den Delegattyp für Sie ableitet, sollte es genau den richtigen Delegattyp erstellen, der für den Parameter benötigt wird.

Wenn dies Ihr spezifisches Anliegen nicht berücksichtigt, stellen Sie bitte ein gutes, minimales , vollständiges Codebeispiel bereit, das das Problem zuverlässig reproduziert, zusammen mit einer klaren, präzisen Erklärung, warum der obige Ansatz nicht hilft.


Abgesehen davon ist es schwer zu sagen, von welchem ​​kleinen Code du gepostet hast, aber es ist ungewöhnlich, eine explizite AddEventHandler() -Methode zu haben. Normalerweise würde eine Klasse einfach ein Ereignis offen legen und Sie würden die += Syntax verwenden, um einen Ereignishandler zu abonnieren.


BEARBEITEN:

Aus Ihren Kommentaren geht hervor, dass Sie von der API zur Einhaltung einer dynamisch bereitgestellten Ereignissignatur verpflichtet sind. Persönlich denke ich, dass diese Art von Design albern ist, aber ich nehme an, dass Sie daran festhalten, vermutlich aufgrund des Designs des Xamarin-Frameworks.

Wenn Sie das angegebene Ziel strikt EventHandler - das heißt, bei einer EventHandler Instanz eine neue Delegat-Instanz erzeugen, deren Typ mit einer vom Run-Time bereitgestellten Type Instanz übereinstimmt - sollte die folgende Methode für Sie funktionieren:

static Delegate CreateDelegate(Type type, EventHandler handler)
{
    return (Delegate)type.GetConstructor(new [] { typeof(object), typeof(IntPtr) })
        .Invoke(new [] { handler.Target, handler.Method.MethodHandle.GetFunctionPointer() });
}

Beispielverwendung:

eventInfo.AddEventHandler(client,
    CreateDelegate(eventInfo.EventHandlerType, (obj, args) => task.Complete());

Du könntest das obige als eine Erweiterungsmethode schreiben, um den Aufruf zu vereinfachen (du hast nicht gesagt, welcher Typ der client ist, also habe ich das object für das Beispiel gemacht):

public static void AddEventHandler(this EventInfo eventInfo, object client, EventHandler handler)
{
        object eventInfoHandler = eventInfo.EventHandlerType
            .GetConstructor(new[] { typeof(object), typeof(IntPtr) })
            .Invoke(new[] { handler.Target, handler.Method.MethodHandle.GetFunctionPointer() });

        eventInfo.AddEventHandler(client, (Delegate)eventInfoHandler);
}

Beispielverwendung:

eventInfo.AddEventHandler(client, (obj, args) => task.Complete());

Geben Sie der Erweiterungsmethode einen anderen Namen, wenn Sie AddEventHandler() dass sich die API-eigene AddEventHandler() -Methode zu einem bestimmten Zeitpunkt so ändern könnte, dass der C # -Compiler anstelle der Erweiterungsmethode seine Implementierung auswählt, oder natürlich heute (Das obige funktioniert, wenn man nur eine einzige AddEventHandler() -Methode mit dem zweiten Parameter als Delegate AddEventHandler() , aber wiederum ... ohne ein gutes, minimales , vollständiges Codebeispiel kann ich nicht garantieren, dass es in Ihrem eigenen Code funktioniert; die Verwendung eines eindeutigen Namens würde garantieren, dass es wird, auf Kosten von ein wenig von der "Magie" auszusetzen :)).


Nachdem ich eine funktionierende Lösung gefunden hatte, konnte ich dann nach ähnlichen Fragen durchsuchen und fand diese, von der Sie argumentieren könnten, dass es sich um ein Duplikat handelt: Reflektion zum Angeben des Typs eines Delegaten (zum Anhängen an ein Ereignis) )

Ich habe beschlossen, meine eigene Antwort hier zu editieren, anstatt nur vorzuschlagen, deine Frage zu schließen, weil die Antwort auf die andere Frage nicht wirklich das gibt, was ich für ein so elegantes oder leicht zu benutzendes Beispiel halte.







xamarin