'The' ответ
(Подробнее читайте ниже, если хотите узнать, как я дошел до этого решения)
Использование, если задан элемент управления с событием vanilla MouseDown
и конкретным событием EventHandler<ValueEventArgs> ValueEvent
:
// 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
(* Это обходной путь от Rx )
(** важно избегать прямого вызова объекта подписчика здесь (например, помещая subscriber.DoSomething (e) или прямого вызова DoSomething (e), если мы находимся внутри класса Subscriber. При этом эффективно создается ссылка абонент, который полностью побеждает объект ...)
Примечание : в некоторых случаях это МОЖЕТ оставлять в памяти ссылки на классы обертывания, созданные для лямбд, но они весят только байты, так что я не слишком обеспокоен.
Реализация:
//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);
}
Деталь
Моя отправная точка была Отличный ответ Егора (см. Ссылку для версии с комментариями):
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;
}
Меня беспокоило то, что событие жестко закодировано в методе. Таким образом, это означает, что для каждого нового события есть новый метод для записи.
Я возился и смог найти это общее решение:
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);
}
Однако проблема этого решения в том, что оно ТОЛЬКО универсально, оно не может обрабатывать стандартные winforms MouseUp, MouseDown и т. Д ...
Итак, я попытался сделать это даже больше generic:
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);
}
Однако, как я намекнул здесь , это не скомпилируется, потому что нет способа ограничить T быть делегатом.
В этот момент я почти сдался. Нет смысла пытаться бороться со спецификациями C #.
Однако вчера я обнаружил метод Observable.FromEvent из среды Reactive, у меня не было реализации, но использование показалось немного знакомым и очень интересным:
var mousedown = Observable.FromEvent<MouseEventHandler, MouseDownEventArgs>(
h => new MouseEventHandler(h),
h => control.MouseDown += h,
h => control.MouseDown -= h);
Это был первый аргумент, который привлек мое внимание. Это обходной путь для отсутствия ограничения типа делегата. Мы берем это, передавая функцию, которая создаст делегата.
Объединение всего этого дает нам решение, показанное вверху этого ответа.
1059 * Запоздалая мысль *
Я настоятельно рекомендовал уделить время, чтобы узнать о реактивной структуре (или как она там называется). Это очень интересно и немного ошеломляет. Я подозреваю, что это также сделает такие вопросы совершенно ненужными.
Пока что самое интересное, что я видел, это видео на Channel9 .