Как я могу получить IObservable <T>в Rx от "нестандартного" события? - PullRequest
6 голосов
/ 26 мая 2010

Вот что я имею в виду.Предположим, я работаю с 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 ]

3 голосов
/ 08 июня 2010

Возможно, вы могли бы просто добавить свою собственную реализацию для пользовательской подписи события?

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
);
1 голос
/ 09 августа 2010

Для дальнейшего использования, это пример использования перегрузки преобразования 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))
1 голос
/ 22 июня 2010

Вы также можете просто сделать это ленивым способом, если updateSource никогда не исчезнет:

var observable = new Subject<BaseEvent>();
updateSource.Update += (o,e) => observable.OnNext(e);

План Джона, вероятно, лучше, но субъекты могут помочь вам.

1 голос
/ 26 мая 2010

Вы можете иметь возможность использовать эту подпись:

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 #, который просто оставил мне заполнить бит подписки, я, конечно, мог бы сделать это ...

0 голосов
/ 17 августа 2013

У меня похожий кошмар, потому что я работаю с 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 в качестве ВХОДА. Обратите внимание, что OUTPUT 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
);

Вот как я понял это с точки зрения новичков, поэтому я надеюсь, что это будет полезно.

0 голосов
/ 04 марта 2011
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 
); 

Убедитесь, что ctor UpdateEventArgs принимает аргумент BaseEvent.

...