Преобразование события без EventArgs с помощью Observable.FromEvent


Я борюсь с преобразованием следующего события в IObservable:

public delegate void _dispSolutionEvents_OpenedEventHandler();
event _dispSolutionEvents_OpenedEventHandler Opened;
Событие исходит из библиотеки, поэтому я не могу его изменить. Перегрузка IObservable.FromEvent, которая должна это сделать, имеет следующую сигнатуру:
public static IObservable<Unit> FromEvent
    ( Action<Action> addHandler
    , Action<Action> removeHandler
    )

Поэтому я попытался преобразовать событие следующим образом:

var opened = Observable.FromEvent
    ( h => _SolutionEvents.Opened += h
    , h => _SolutionEvents.Opened -= h
    );

Но компилятору не нравятся _SolutionEvents.Opened += h и _SolutionEvents.Opened += h, потому что

Не может неявно преобразовать тип ' System.Действие" to " EnvDTE._dispSolutionEvents_OpenedEventHandler'.

Я не думайте, что я могу просто сказать _SolutionEvents.Opened += new _dispSolutionEvents_OpenedEventHandler(h), потому что тогда удаление не будет работать, потому что у меня есть другой экземпляр, верно?

Существует еще одна перегрузка Observable.FromEvent со следующей сигнатурой:

public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>
    ( Func<Action<TEventArgs>, TDelegate> conversion
    , Action<TDelegate> addHandler
    , Action<TDelegate> removeHandler
    )

Этот метод позволяет преобразовать действие в обработчик событий, но, похоже, он работает только с TEventArgs.

Rx пропускает соответствующую перегрузку или я что-то упускаю?

2 7

2 ответа:

Оказывается, что использовать шаблон FromEvent очень просто.

Просто сделайте это:

var opened = Observable.FromEvent<_dispSolutionEvents_OpenedEventHandler, Unit>(
    h => () => h(Unit.Default),
    h => _SolutionEvents.Opened += h,
    h => _SolutionEvents.Opened -= h);

Я проверил наблюдаемое с помощью этого кода:

void Main()
{
    var _SolutionEvents = new Foo();

    var opened = Observable.FromEvent<_dispSolutionEvents_OpenedEventHandler, Unit>(h => () => h(Unit.Default), h => _SolutionEvents.Opened += h, h => _SolutionEvents.Opened -= h);

    opened.Subscribe(x => Console.WriteLine("Opened"));

    _SolutionEvents.OnOpened();
}

public delegate void _dispSolutionEvents_OpenedEventHandler();

public class Foo
{
    public event _dispSolutionEvents_OpenedEventHandler Opened;

    public void OnOpened()
    {
        this.Opened?.Invoke();
    }
}

Он производит следующий ожидаемый результат:

Opened
Стоит отметить, что интерфейса IObservable нет, а есть только IObservable<T>, поэтому вы должны что-то вернуть. Фокус здесь в том, чтобы преобразовать delegate void _dispSolutionEvents_OpenedEventHandler() в IObservable<Unit>, чтобы заставить его работать, и это то, что делает h => () => h(Unit.Default).

Здесь вы сталкиваетесь с проблемой типа А. Тип _dispSolutionEvents_OpenedEventHandler не является Action. Он выглядит как Тип Action, но это не Тип Action.

IMO это событие не соответствует стандартам .NET для событий. В общем случае делегат будет соответствовать шаблону принятия параметра sender object и подкласса EventArg для второго параметра.

Т. е.

public delegate void _dispSolutionEvents_OpenedEventHandler(object sender, EventArgs e);

Если вы попытаетесь просто присоединить Action к событию, вы обнаружите, что это тоже не удается.

Action onOpened = ()=>Console.WriteLine("Opened");
_SolutionEvents.Opened += onOpened;  //CS0029 Cannot implicitly convert type 'System.Action' to '_dispSolutionEvents_OpenedEventHandler'

В компилятор достаточно умен, чтобы сделать некоторый вывод типа, если вы сделаете это;

_SolutionEvents.Opened+= () => Console.WriteLine("Opened");

Но когда вы используете Rx, вы уже набраны в Тип Action, поэтому эффективно возвращаетесь к предыдущему вопросу выше.

Если владелец библиотеки был хорошим, событие будет следовать обычному шаблону sender/eventArgs. В противном случае они, по крайней мере, определили бы делегат как просто Action вместо их собственного метода клиента без параметров, void. :- /

Итак, поскольку событие, которое у вас есть, не в соответствии со стандартными шаблонами .NET, вам нужно будет предоставить Rx еще некоторое ручное удержание (виноват ваш поставщик библиотеки, а не Rx).

Вы могли бы бороться с FromEvent/FromEventPattern но поскольку ваша библиотека не соответствует духу события, я бы предложил просто использовать Observable.Create, который, по крайней мере, сохраняет код очевидным, что происходит, и должен позволить следующему пользователю лучше понять его.

Observable.Create<Unit>(obs =>
{
    _dispSolutionEvents_OpenedEventHandler handler = () => obs.OnNext(Unit.Default);

    _SolutionEvents.Opened += handler;
    return System.Reactive.Disposables.Disposable.Create(() => _SolutionEvents.Opened -= handler);
});