如何从“非标准”事件中获取Rx中的IObservable <t>?</t>

时间:2010-05-26 16:49:27

标签: .net vb.net system.reactive

这就是我的意思。假设我正在使用公开事件的API,但这些事件不符合标准EventHandlerEventHandler<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> < / em>的。但这是一项烦人的工作。

有没有办法从像这样的“非标准”事件中创建IObservable<[something]>,还是我运气不好?


更新:从Jon Skeet的回答中,我向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 个答案:

答案 0 :(得分:3)

也许您可以为自定义事件签名添加自己的实现?

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 :(得分:1)

可以能够使用此签名:

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去decypher它,但我确定那里有一个委托类型)。 TEventArgs将是事件参数的类型(我认为BaseEvent应该在这里执行)。您需要提供从EventHandler(Of BaseEvent)到委托类型的转换器 - 这可能只是一个lambda表达式,用于传递给它的参数来调用给定的事件处理程序。添加和删​​除操作将是正常的事件订阅代码 - 但表示为代理。

不幸的是,我的VB不够好,不能很好地表达所有这些 - 或者确实知道在VB 9或10中有多少容易获得。我知道如何在C#中看到它...如果你能给我C#中的一个简短但完整的例子让我填写订阅位,我当然可以这样做......

答案 2 :(得分:1)

如果updateSource永远不会消失,你也可以以懒惰的方式做到这一点:

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

但Jon的计划可能更好,但受试者可以帮助你。

答案 3 :(得分:1)

为了将来参考,这是使用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))

答案 4 :(得分:0)

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参数。

答案 5 :(得分:0)

我有一个类似的噩梦,因为我正在使用一个使用非标准.net事件的互操作API。我是很多东西的初学者,包括Generics,Funcs,Actions,Observables和Rx,所以我相信我对如何理解这些东西的经验会有一些价值。

我们可以通过了解它的使用位置来了解Func(Of EventHandler(Of TEventArgs), TDelegate) conversion

但首先我们必须了解FromEvent方法的泛型签名。

下面是vb中的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))

但我并不真正使用vb所以我将不得不求助于c#签名:

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

让我们逐行分析c#签名。

注意:有时我会在lambda表达式中包含数据类型,以区分标准.net事件和非标准事件。

第一部分:

IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(

这表示函数FromEvent接受TDelegateTEventArgs As EventArgs作为INPUT。请注意,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>(

第二部分:

接下来,我们有三个输入conversionaddhandlerremovehandler

Func<Action<TEventArgs>, TDelegate> conversion, 
Action<TDelegate> addHandler, 
Action<TDelegate> removeHandler);

我们知道addHandlerremoveHandler只是添加和删除事件中的委托。我们先做两个。

// 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
);

这就是我对新手观点的理解,所以我希望它会有所帮助。