C# 如何使用反应式扩展实现事件

C# 如何使用反应式扩展实现事件,c#,events,system.reactive,C#,Events,System.reactive,反应式扩展允许您使用Observable.FromEventPattern轻松订阅事件,但我找不到任何关于当您有IObservable时如何实现事件的信息 我的情况是:我需要实现一个包含事件的接口。该事件应该在对象的某个值更改时调用,出于线程安全原因,我需要在某个SynchronizationContext上调用该事件。我还应该在注册时使用当前值调用每个事件处理程序 public interface IFooWatcher { event FooChangedHandler FooCha

反应式扩展允许您使用
Observable.FromEventPattern
轻松订阅事件,但我找不到任何关于当您有
IObservable
时如何实现事件的信息

我的情况是:我需要实现一个包含事件的接口。该事件应该在对象的某个值更改时调用,出于线程安全原因,我需要在某个
SynchronizationContext
上调用该事件。我还应该在注册时使用当前值调用每个事件处理程序

public interface IFooWatcher
{
    event FooChangedHandler FooChanged;
}
使用Rx,使用
行为主体
,获得符合我要求的可观察对象相当容易:

public class FooWatcher
{
    private readonly BehaviorSubject<Foo> m_subject;
    private readonly IObservable<Foo> m_observable;

    public FooWatcher(SynchronizationContext synchronizationContext, Foo initialValue)
    {
        m_subject = new BehaviorSubject<Foo>(initialValue);
        m_observable = m_subject
            .DistinctUntilChanged()
            .ObserveOn(synchronizationContext);
    }

    public event FooChangedHandler FooChanged
    {
        add { /* ??? */ }
        remove { /* ??? */ }
    }
}
但是,我希望找到一个更简单的解决方案,因为我需要实现其中几个事件(不同的处理程序类型)。我试图推出我自己的通用解决方案,但它产生了一些需要解决的其他问题(特别是,如何使用参数为
T
)的委托,因此,我更愿意找到一个现有的解决方案来弥补这个方向上的差距,就像EventPattern中的
起到了相反的作用一样。

您可以这样做:

public event FooChangedHandler FooChanged
{
    add { m_observable.ToEvent().OnNext += value; }
    remove { m_observable.ToEvent().OnNext -= value; }
}
然而,在删除时,我认为您可能只是想处理订阅。。。或者从ToEvent()获取操作并将其存储为成员。未经测试

编辑:但是,您必须使用Action而不是FoodChangedHandler委托

编辑2:这是一个经过测试的版本。但是,我想您需要使用FoodChangedHandler,因为您有一堆这些预先存在的处理程序

void Main()
{
    IObservable<Foo> foos = new [] { new Foo { X = 1 }, new Foo { X = 2 } }.ToObservable();
    var watcher = new FooWatcher(SynchronizationContext.Current, new Foo { X = 12 });
    watcher.FooChanged += o => o.X.Dump();  
    foos.Subscribe(watcher.Subject.OnNext); 
}

// Define other methods and classes here

//public delegate void FooChangedHandler(Foo foo);
public interface IFooWatcher
{
    event Action<Foo> FooChanged;
}

public class Foo {
    public int X { get; set; }
}
public class FooWatcher
{

    private readonly BehaviorSubject<Foo> m_subject;
    public BehaviorSubject<Foo> Subject { get { return m_subject; } }
    private readonly IObservable<Foo> m_observable;

    public FooWatcher(SynchronizationContext synchronizationContext, Foo initialValue)
    {
        m_subject = new BehaviorSubject<Foo>(initialValue);

        m_observable = m_subject
            .DistinctUntilChanged();
    }

    public event Action<Foo> FooChanged
    {
        add { m_observable.ToEvent().OnNext += value; }
        remove { m_observable.ToEvent().OnNext -= value; }
    }
}
void Main()
{
IObservable foos=new[]{new Foo{X=1},new Foo{X=2}}.ToObservable();
var-watcher=newfoowatter(SynchronizationContext.Current,newfoo{X=12});
watcher.FooChanged+=o=>o.X.Dump();
订阅(watcher.Subject.OnNext);
}
//在此处定义其他方法和类
//公共代表无效FooChangedHandler(Foo-Foo);
公共接口IFooWatcher
{
事件行为发生变化;
}
公开课Foo{
公共整数X{get;set;}
}
公共类食品观察者
{
私有只读行为主体m_subject;
公共行为主体{get{return m_Subject;}}
私有只读IOobservable m_observable;
公共FooWatcher(SynchronizationContext SynchronizationContext,Foo initialValue)
{
m_subject=新行为主体(初始值);
m_可观察=m_受试者
.DistinctUntilChanged();
}
公众活动行动计划
{
添加{m_observable.ToEvent().OnNext+=value;}
删除{m_observable.ToEvent().OnNext-=value;}
}
}

考虑到您已经在混合反应性代码和更普通的代码之间的界限,您可以做一个反应性更小的版本。要开始,只需声明一个普通事件模式

public event FooChangedHandler FooChanged;

protected void OnFooChanged(Foo)
{
  var temp = FooChanged;
  if (temp != null)
  {  
    temp(new FooChangedEventArgs(Foo));
  }
}
然后在构造器中简单地将可观察对象连接到它

m_Observable.Subscribe(foo => OnFooChanged(foo));

它不是很简单,但非常简单。

谢谢,我以前没有看过
ToEvent()
!这已经是谜题的一大块了。
Action
FooChangedHandler
之间的转换仍然是一个问题(是的,不幸的是,我们一直在处理这些问题)。问题:如果代理具有相同的目标和方法,则它们是相等的,但这种相等仅达到一个级别-如果您有一个代理并将其包装在两个单独的
操作
实例中,则它们将相等,但如果您将这两个
操作
实例包装在另一个
操作
中,则它们将不相等。然而,我想我已经看到了解决这个问题的方法……啊,是的,这里是:我明天会测试这个,再次感谢!现在一切正常。使用类似上面链接的解决方案将代理转换为
操作
,可以让我编写一个简短的通用解决方案,在内部使用
ToEvent()
。该类没有太多内容,但我想将
主题
IEventSource
捆绑在一个对象中。很抱歉再次收回accept,但我注意到您的示例代码存在严重问题。调用
ToEvent
将始终创建一个新的
IEventSource
,因此您的代码将尝试从其注册的不同事件中注销。我试过了,但肯定不行。调用
ToEvent
一次并存储结果。一旦你解决了这个问题,我会重新接受的。好吧,酷,我今天晚些时候/今晚试着去做。。。我今天有考试。干杯。您可以在一开始就为
FooChanged
分配一个
delegate{}
,将此解决方案折叠为两行,从而完全省去
OnFooChanged
。但是,这种方法不符合我的要求,因为事件的新订阅者不接收当前状态。此外,由于my
SyncrhonizationContext
本质上是发布到事件循环,因此新订阅者可以接收在订阅建立之前发生的更改(使用Java内存模型术语)。这可能不是问题,但我宁愿避免它。
m_Observable.Subscribe(foo => OnFooChanged(foo));