"La" réponse
(Lire plus ci-dessous si vous voulez voir comment j'en suis arrivé à cette solution)
L'utilisation, compte tenu d'un contrôle de vanille MouseDown
événement, et une EventHandler<ValueEventArgs> ValueEvent
événement:
// for 'vanilla' events
SetAnyHandler<Subscriber, MouseEventHandler, MouseEventArgs>(
h => (o,e) => h(o,e), //don't ask me, but it works*.
h => control.MouseDown += h,
h => control.MouseDown -= h,
subscriber,
(s, e) => s.DoSomething(e)); //**See note below
// for generic events
SetAnyHandler<Subscriber, ValueEventArgs>(
h => control.ValueEvent += h,
h => control.ValueEvent -= h,
subscriber,
(s, e) => s.DoSomething(e)); //**See note below
(*C'est une solution de contournement de Rx)
(** il est important d'éviter d'invoquer l'objet de l'abonné directement ici (par exemple mettre de l'abonné.DoSomething(e), ou en invoquant DoSomething(e) directement si nous sommes à l'intérieur de l'Abonné de la classe. En faisant cela crée effectivement une référence à l'abonné, ce qui va à l'encontre de l'objet...)
Remarque: dans certaines circonstances, cela PEUT laisser les références à l'emballage des classes créées pour les lambdas dans la mémoire, mais ils ne pèsent octets, donc je ne suis pas trop gêné.
Mise en œuvre:
//This overload handles any type of EventHandler
public static void SetAnyHandler<S, TDelegate, TArgs>(
Func<EventHandler<TArgs>, TDelegate> converter,
Action<TDelegate> add, Action<TDelegate> remove,
S subscriber, Action<S, TArgs> action)
where TArgs : EventArgs
where TDelegate : class
where S : class
{
var subs_weak_ref = new WeakReference(subscriber);
TDelegate handler = null;
handler = converter(new EventHandler<TArgs>(
(s, e) =>
{
var subs_strong_ref = subs_weak_ref.Target as S;
if(subs_strong_ref != null)
{
action(subs_strong_ref, e);
}
else
{
remove(handler);
handler = null;
}
}));
add(handler);
}
// this overload is simplified for generic EventHandlers
public static void SetAnyHandler<S, TArgs>(
Action<EventHandler<TArgs>> add, Action<EventHandler<TArgs>> remove,
S subscriber, Action<S, TArgs> action)
where TArgs : EventArgs
where S : class
{
SetAnyHandler<S, EventHandler<TArgs>, TArgs>(
h => h, add, remove, subscriber, action);
}
Le détail
Mon point de départ était Egor's excellente réponse (voir le lien pour la version avec commentaires):
public static void Link(Publisher publisher, Control subscriber) {
var subscriber_weak_ref = new WeakReference(subscriber);
EventHandler<ValueEventArgs<bool>> handler = null;
handler = delegate(object sender, ValueEventArgs<bool> e) {
var subscriber_strong_ref = subscriber_weak_ref.Target as Control;
if (subscriber_strong_ref != null) subscriber_strong_ref.Enabled = e.Value;
else {
((Publisher)sender).EnabledChanged -= handler;
handler = null;
}
};
publisher.EnabledChanged += handler;
}
Ce qui m'a dérangé est que l'événement est codé en dur dans la méthode. Ce qui signifie que pour chaque nouvel événement, il y a une nouvelle méthode pour écrire.
J'ai trafiqué autour et réussi à venir avec cette solution générique:
private static void SetAnyGenericHandler<S, T>(
Action<EventHandler<T>> add, //to add event listener to publisher
Action<EventHandler<T>> remove, //to remove event listener from publisher
S subscriber, //ref to subscriber (to pass to action)
Action<S, T> action) //called when event is raised
where T : EventArgs
where S : class
{
var subscriber_weak_ref = new WeakReference(subscriber);
EventHandler<T> handler = null;
handler = delegate(object sender, T e)
{
var subscriber_strong_ref = subscriber_weak_ref.Target as S;
if(subscriber_strong_ref != null)
{
Console.WriteLine("New event received by subscriber");
action(subscriber_strong_ref, e);
}
else
{
remove(handler);
handler = null;
}
};
add(handler);
}
Cependant, le problème avec cette solution est qu'elle est générique, il ne peut pas gérer le standard winforms MouseUp, MouseDown, etc...
J'ai donc essayé de le rendre encore plus générique:
private static void SetAnyHandler<T, R>(
Action<T> add, //to add event listener to publisher
Action<T> remove, //to remove event listener from publisher
Subscriber subscriber, //ref to subscriber (to pass to action)
Action<Subscriber, R> action)
where T : class
{
var subscriber_weak_ref = new WeakReference(subscriber);
T handler = null;
handler = delegate(object sender, R e) //<-compiler doesn't like this line
{
var subscriber_strong_ref = subscriber_weak_ref.Target as Subscriber;
if(subscriber_strong_ref != null)
{
action(subscriber_strong_ref, e);
}
else
{
remove(handler);
handler = null;
}
};
remove(handler);
}
Cependant, comme je l'ai déjà évoqué ici, ce ne compile pas, car il n'existe aucun moyen de restreindre T à un délégué.
À ce moment, j'ai à peu près abandonné. Il n'y a pas de point d'essayer de se battre avec le C# specs.
Cependant, hier, j'ai découvert l'Observable.FromEvent méthode de la Réactif cadre, je n'avais pas la mise en œuvre, mais l'usage semble un peu familier, et très intéressant:
var mousedown = Observable.FromEvent<MouseEventHandler, MouseDownEventArgs>(
h => new MouseEventHandler(h),
h => control.MouseDown += h,
h => control.MouseDown -= h);
Il a été le premier argument qui a attiré mon attention. C'est la solution de contournement pour l'absence d'un délégué type de contrainte. Nous prenons en passant dans la fonction qui va créer le délégué.
Mettant tout cela ensemble nous donne la solution indiquée en haut de cette réponse.
Après coup
J'ai tout à fait recommandé de prendre le temps d'apprendre le réactif de cadre (ou de ce qu'il finit par être appelé). Il est TRÈS intéressant, et un peu mindblowing. Je soupçonne que ce sera aussi rendu à ce genre de questions totalement superflus.
Jusqu'à présent, la plupart des choses intéressantes que j'ai vu les vidéos sur Channel 9.