Как я могу получить IObservable<T> в Rx из "нестандартного" события?
Вот что я имею в виду. Предположим, я работаю с API, который предоставляет события, но эти события не соответствуют стандарту. EventHandler
или же EventHandler<TEventArgs>
подпись. Например, одно событие может выглядеть так:
Public Event Update(ByVal sender As BaseSubscription, ByVal e As BaseEvent)
Теперь, как правило, если я хочу получить IObservable<TEventArgs>
из события, я могу просто сделать это:
Dim updates = Observable.FromEvent(Of UpdateEventArgs)( _
target:=updateSource, _
eventName:="Update" _
)
Но это не работает, потому что Update
событие не EventHandler<UpdateEventArgs>
- на самом деле, нет UpdateEventArgs
- это в основном просто своя вещь.
Очевидно, я мог бы определить свой собственный класс, производный от EventArgs
(То есть, UpdateEventArgs
), напишите другой класс, чтобы обернуть объект, предоставляя Update
событие, дать класс обертки свой собственный Update
событие, которое является EventHandler<UpdateEventArgs>
и получить IObservable<UpdateEventArgs>
из этого. Но это надоедливый объем работы.
Есть ли способ создать IObservable<[something]>
из "нестандартного" события, как это, или мне не повезло?
ОБНОВЛЕНИЕ: от ответа Джона Скита, я подталкиваю в направлении следующей перегрузки Observable.FromEvent
:
Function FromEvent(Of TDelegate, TEventArgs As EventArgs)( _
conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _
addHandler As Action(Of TDelegate), _
removeHandler As Action(Of TDelegate) _
) As IObservable(Of IEvent(Of TEventArgs))
Я должен признать, что у меня проблемы с тем, чтобы обернуть голову вокруг этого Func(Of EventHandler(Of TEventArgs), TDelegate)
часть. Мне кажется задом наперед (?). Очевидно, что я просто что-то упускаю...
Во всяком случае, если это поможет, я думаю, что это будет выглядеть эквивалентный код C# (я буду совершенно честен: я не уверен в этом. Хотя я обычно предпочитаю сам C#, этот код - работа одного из моих коллег, которые пишут в основном в VB.NET и VB.NET разрешает несколько синтаксисов для объявления событий):
// notice: not an EventHandler<TEventArgs>
public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e);
// not 100% sure why he did it this way
public event UpdateEventHandler Update;
Сложность в том, что кажется, что какой- то класс EventArgs
необходимо, несмотря ни на что. В API, с которым я работаю, такого класса нет. Итак, минимум, мне придется написать один. Но это должно быть довольно тривиально (в основном одно свойство: BaseEvent
).
В конце я предполагаю, что код, необходимый для этой перегрузки, будет выглядеть примерно так в C#:
var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>(
// conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>)
handler => (sender, e) => handler(sender, new UpdateEventArgs(e)),
// addHandler (Action<UpdateEventHandler>)
handler => updateSource.Update += handler,
// removeHandler (Action<UpdateEventHandler>)
handler => updateSource.Update -= handler
);
Прежде всего: у меня даже есть это прямо? Во-вторых, правильно ли я сказал, что, используя VB 9, на самом деле нет способа выполнить вышеизложенное без написания собственных методов?
Мне почти кажется, что я подхожу к этой проблеме с совершенно неправильной точки зрения. Но я действительно не уверен.
6 ответов
Возможно, вы могли бы просто добавить свою собственную реализацию для пользовательской подписи события?
public interface ICustomEvent<TSource, TArgs>
{
public TSource Source { get; }
public TArgs EventArgs { get; }
}
public interface CustomEvent<TSource, TArgs> : ICustomEvent<TSource, TArgs>
{
public TSource Source { get; set; }
public TArgs EventArgs { get; set; }
}
public static class ObservableEx
{
public static IObservable<ICustomEvent<TSource, TArgs>> FromCustomEvent(
Action<Action<TSource, TArgs>> addHandler,
Action<Action<TSource, TArgs>> removeHandler)
{
return Observable.CreateWithDisposable(observer =>
{
Action<TSource, TArgs> eventHandler = (s,a) =>
observer.OnNext(new CustomEvent<TSource,TArgs>(s,a));
addHandler(eventHandler);
return Disposable.Create(() => removeHandler(eventHandler));
});
}
}
Тогда вы можете использовать его как:
var observable = ObservableEx.FromCustomEvent<BaseSubscription,BaseEvent>(
h => updateSource.Update += h,
h => updateSource.Update -= h
);
Вы также можете просто сделать это ленивым способом, если updateSource никогда не исчезнет:
var observable = new Subject<BaseEvent>();
updateSource.Update += (o,e) => observable.OnNext(e);
План Джона, вероятно, лучше, хотя субъекты могут помочь вам.
Вы можете использовать эту подпись:
Public Shared Function FromEvent(Of TDelegate, TEventArgs As EventArgs) ( _
conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _
addHandler As Action(Of TDelegate), _
removeHandler As Action(Of TDelegate) _
) As IObservable(Of IEvent(Of TEventArgs))
Вот, TDelegate
будет тип делегата события (что я не могу сразу сказать из вашего объявления - объявления событий в C# выглядят не совсем так, и я боюсь, я не знаю достаточно VB, чтобы расшифровать его, но я ' я уверен, что где-то есть тип делегата). TEventArgs
будет тип для аргумента события (BaseEvent
Я должен сделать это здесь, я думаю). Вам нужно будет предоставить конвертер из EventHandler(Of BaseEvent)
к вашему типу делегата - это, вероятно, будет просто лямбда-выражением для вызова данного обработчика события с передаваемыми в него аргументами. Действия добавления и удаления были бы обычным кодом подписки на события, но выражались как делегаты.
К сожалению, мой VB не достаточно хорош, чтобы выразить все это аккуратно - или даже знать, сколько всего легко доступно в VB 9 или 10. Я знаю, как все это будет выглядеть в C#... если бы вы могли дать мне короткую, но полный пример в C#, который просто оставил меня, чтобы заполнить бит подписки, я, конечно, мог бы сделать это...
Для дальнейшего использования это пример использования перегрузки преобразования FromEvent с использованием FileSystemEventHandler в качестве примера:
Dim createWatcher As New FileSystemWatcher With {.Path = "C:\Temp", .EnableRaisingEvents = True}
Dim foo = Observable.FromEvent(Of FileSystemEventHandler, FileSystemEventArgs)(
Function(ev) New FileSystemEventHandler(Sub(o, e) ev(o, e)),
Sub(ev) AddHandler createWatcher.Created, ev,
Sub(ev) RemoveHandler createWatcher.Created, ev)
Dim changedEv = Observable.FromEvent(Of FileSystemEventHandler, FileSystemEventArgs)(
Function(ev) New FileSystemEventHandler(Sub(o, e) ev(o, e)),
Sub(ev) AddHandler createWatcher.Changed, ev,
Sub(ev) RemoveHandler createWatcher.Changed, ev)
foo.Subscribe(Sub(e) Console.WriteLine("File {0} created.", e.EventArgs.Name))
changedEv.Subscribe(Sub(e) Console.WriteLine("File {0} changed.", e.EventArgs.Name))
У меня похожий кошмар, потому что я работаю с API взаимодействия, использующим нестандартные события.net. Я новичок во многих вещах, включая Generics, Funcs, Actions, Observables и Rx, поэтому я верю, что мой опыт понимания таких вещей будет иметь определенную ценность.
Мы можем получить вашу голову вокруг Func(Of EventHandler(Of TEventArgs), TDelegate) conversion
понимая, где это используется.
Но сначала мы должны понять общую подпись FromEvent
метод.
Ниже приведена сигнатура функции метода расширения FromEvent в vb:
Function FromEvent(Of TDelegate, TEventArgs As EventArgs)( _
conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _
addHandler As Action(Of TDelegate), _
removeHandler As Action(Of TDelegate) _
) As IObservable(Of IEvent(Of TEventArgs))
Но я не пользуюсь vb, поэтому мне придется прибегнуть к сигнатуре C#:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
Func<Action<TEventArgs>, TDelegate> conversion,
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler);
Давайте проанализируем подпись C# построчно.
Примечание. Будут случаи, когда я буду включать типы данных в лямбда-выражения, чтобы отличать стандартные.net-события от нестандартных.
Первая часть:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
Это говорит о том, что функция FromEvent принимает TDelegate
а также TEventArgs As EventArgs
как ВВОД. Обратите внимание, что выход IObservable
также будет иметь тип TEventArgs
Таким образом, вы правы, когда сказали, что вам нужен класс, который обернет данные TDelegate
, Я не знаю, какую версию я использую, но это позволяет мне использовать любой класс, даже если он не наследуется от EventArgs
, В случае, если VB не позволяет вам, это все равно тривиальная работа, так как он просто добавляет :EventArgs
к классу (наследует от vb?). Давайте применим это к вашей проблеме:
Ваши предположения C#:
// notice: not an EventHandler<TEventArgs> public delegate void
public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e);
// class to wrap the data from the above delegate
public class UpdateEventArgs:EventArgs {...}
Применение к первой строке становится:
var updates = FromEvent<UpdateEventHandler, UpdateEventArgs>(
Вторая часть:
Далее у нас есть три входа conversion
, addhandler
, а также removehandler
:
Func<Action<TEventArgs>, TDelegate> conversion,
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler);
Мы знаем это addHandler
а также removeHandler
просто добавляет и удаляет делегата из события. Давайте сделаем эти два в первую очередь.
// addHandler (Action<UpdateEventHandler>)
handler => updateSource.Update += handler,
// removeHandler (Action<UpdateEventHandler>)
handler => updateSource.Update -= handler
Теперь давайте применим наш тип к сложной части:
Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion,
Эта функция занимает Action<UpdateEventArgs>
в качестве входа и UpdateEventHandler
делегат это выход. Давайте назначим его в переменной с именем conversion
Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion
= (handlerOfUpdateEventArgs) =>
{
UpdateEventHandler handler = (BaseSubscription sender, BaseEvent e) =>
handlerOfUpdateEventArgs(sender, new UpdateEventArgs(e));
return handler;
};
Чтобы лучше понять, что это делает, давайте посмотрим, как мы прикрепляем обработчик событий к стандартному событию.net:
someObject.SomeEvent += (object sender,EventArgs args) => { ... };
Теперь мы посмотрим на ваше нестандартное событие.net и UpdateEventHandler
:
public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e);
updateSource.Update += (BaseSubscription sender, BaseEvent e) => { ... };
Если вы посмотрите на подпись conversion
функция, она возвращает UpdateEventHandler
делегировать. Это будет означать, что мы можем использовать conversion
прикрепить к Update
событие. Но прежде чем мы сможем это сделать, conversion
функция нуждается в Action<UpdateEventArgs>
в качестве ввода, прежде чем он может быть использован. Давайте сделаем это сейчас:
//EventHandler<EventArgs> similarity.
Action<UpdateEventArgs> actionUpdateEventArgs = (UpdateEventArgs e) =>
{
//This is were you put your code like in a standard.net event
//This is also probably where the Observable.FromEvent() puts
//wiring necessary to make it into an IObservable<UpdateEventArgs>
};
Теперь, когда у нас есть все части, которые нам нужны, мы можем использовать conversion
похож на обработчик событий.
updateSource.Update += conversion(actionUpdateEventArgs);
Код внутри actionUpdateEventArgs
будет вызываться каждый раз Update
Поднялся.
Надеюсь, этого было достаточно, чтобы понять параметр Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion
,
Наконец, вот как вы будете использовать FromEvent()
метод:
Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion
= (handlerOfUpdateEventArgs) =>
{
UpdateEventHandler handler = (BaseSubscription sender, BaseEvent e) =>
handlerOfUpdateEventArgs(sender, new UpdateEventArgs(e));
return handler;
};
var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>(
// conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>)
conversion,
// addHandler (Action<UpdateEventHandler>)
handler => updateSource.Update += handler,
// removeHandler (Action<UpdateEventHandler>)
handler => updateSource.Update -= handler
);
Вот как я понял это с точки зрения новичков, поэтому я надеюсь, что это будет полезно.
var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>(
// conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>)
handler => (BaseSubscription sender, BaseEvent e) => handler.Invoke(sender, new UpdateEventArgs(e)),
// addHandler (Action<UpdateEventHandler>)
handler => updateSource.Update += handler,
// removeHandler (Action<UpdateEventHandler>)
handler => updateSource.Update -= handler
);
Убедитесь, что UpdateEventArgs ctor принимает аргумент BaseEvent.