.net 观察者模式变体(IObservable 和事件委托)以及何时使用它们之间有什么区别?

Hag*_*Aly 1 c# design-patterns reactive-programming

我只想知道何时使用每个以及每个的优点我真的很难理解为什么 .net 在拥有事件委派后引入 IObservable/IObserver 并且根据 MSDN 事件委派是首选

基于我们对观察者模式的理解,现在让我们将注意力转向在 .NET Framework 中使用这种模式。熟悉 FCL 中公开的类型的人会注意到框架中不存在 IObserver、IObservable 或 ObservableImpl 类型*。它们缺席的主要原因是 CLR 使它们过时了。尽管您当然可以在 .NET 应用程序中使用这些构造,但引入 *delegates 和 events 提供了一种新的强大方法来实现观察者模式,而无需开发专用于支持此模式的特定类型。事实上,由于委托和事件是 CLR 的第一类成员,因此该模式的基础已整合到 .NET Framework 的核心中。像这样,

那么为什么他们将 IObservable 添加到 .net 4.0

Eni*_*ity 6

我在MSDN站点上找到了对引用文本的引用。我不得不说我惊呆了。这似乎是对IObservable<T>.

那些你在FCL暴露的类型,通过熟悉会注意到没有IObserverIObservableObservableImpl类型是存在于框架。

这是对的。该IObservable<T>IObserver<T>接口,我们推导数学双的IEnumerable<T>IEnumerator<T>。它将集合从您同步请求值的内容转变为异步将值推送给您的内容。以下是Matthew Podwysocki关于二元性的评论:

我们记得在本系列的第一篇文章中,我们讨论了拉(交互式)与推送(反应)模型。拉模型,由IEnumerable<T>/的迭代器模式IEnumerator<T>表示,我们必须显式调用一个方法,以便从我们的抽象集合中获取每个项目。另一方面,我们的推送模型,由IObservable<T>/的可观察模式IObserver<T>表示,我们通过订阅注册兴趣,然后项目随后从一些抽象集合交给我们。

回到你引用的文本。

它们缺席的主要原因是 CLR 使它们在流行之后过时了。尽管您当然可以在 .NET 应用程序中使用这些构造,但委托和事件的引入提供了一种新的强大方法来实现观察者模式,而无需开发专用于支持此模式的特定类型。

这对我来说似乎完全倒退。自 v1.0 以来,委托和事件一直在框架中。如果他们制作IObservable<T>/IObserver<T>过时,那么就没有必要介绍它们(这是您问题的症结所在)。

我当时的记忆是,Microsoft 非常相信这对接口的价值,以至于他们急于将它们包含在 BCL 中,以便开发人员可以在完整System.Reactive实现发布之前编写自己的基本代码。

事实上,由于委托和事件是 CLR 的第一类成员,因此该模式的基础已整合到 .NET Framework 的核心中。因此,FCL 在其整个结构中广泛使用了观察者模式。

这又是对“头等舱成员”含义的兴趣看法。维基百科说:

在编程语言设计中,给定编程语言中的一等公民(也称为类型、对象、实体或值)是支持其他实体通常可用的所有操作的实体。这些操作通常包括作为参数传递、从函数返回、修改和分配给变量。

事件当然不是一等公民。您不能传递事件,也不能向声明它的类独立地引发事件。

举这个简单的例子:

void Main()
{
    var foo = new Foo();
    EventHandler bar = foo.Bar;
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试编译时出现以下错误:

CS0070 事件 'Foo.Bar' 只能出现在 += 或 -= 的左侧(除非在类型 'Foo' 中使用)

如果我有对定义事件的类的实例的引用,我只能订阅事件,并且我只能从同一个类中引发事件。

Observable 并非如此。

这段代码编译得很好:

void Main()
{
    var foo = new Foo();
    IObservable<EventPattern<EventArgs>> bar =
        Observable
            .FromEventPattern<EventHandler, EventArgs>(
                h => foo.Bar += h,
                h => foo.Bar -= h);
}

public void SimpleExample(IObservable<EventPattern<EventArgs>> example)
{
    example.Subscribe(x => { });
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}
Run Code Online (Sandbox Code Playgroud)

Observable 是 C#(以及 VB.NET 和 F#)的一等公民,但事件不是。

虽然标准事件模型是观察者模式的一种形式,但它并不总是易于使用。

试试这个代码:

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

    foo.Bar += (s, e) => Console.WriteLine("Bar!");
    foo.Bar -= (s, e) => Console.WriteLine("Bar!");

    foo.OnBar();
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}
Run Code Online (Sandbox Code Playgroud)

运行时它仍然会产生“Bar!” 在控制台上。

要正确取消订阅,您必须保留对原始处理程序的引用。这有效:

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

    EventHandler handler = (s, e) => Console.WriteLine("Bar!");
    foo.Bar += handler;
    foo.Bar -= handler;

    foo.OnBar();
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}
Run Code Online (Sandbox Code Playgroud)

Observables 处理得更干净:

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

    IObservable<EventPattern<EventArgs>> bar =
        Observable
            .FromEventPattern<EventHandler, EventArgs>(
                h => foo.Bar += h,
                h => foo.Bar -= h);

    IDisposable subscription = SimpleAttach(bar);
    SimpleDetach(subscription);

    foo.OnBar();        
}

public IDisposable SimpleAttach(IObservable<EventPattern<EventArgs>> example)
{
    return example.Subscribe(x => Console.WriteLine("Bar!"));
}

public void SimpleDetach(IDisposable subscription)
{
    subscription.Dispose();
}   

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}
Run Code Online (Sandbox Code Playgroud)

不仅可以传递事件(可观察的),而且可以在不参考原始处理程序本身的情况下传递分离处理程序的能力。在我的示例Console.WriteLine("Bar!")中,甚至与取消订阅的方法不同。

这导致能够做一些事情,比如List<IDisposable> disposables将所有订阅存储在一个地方,可以用来干净地从所有事件中分离出来。就做disposables.ForEach(x => x.Dispose);

Observables 非常适合在单个查询中组合多个范式。像这样:

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

    IObservable<EventPattern<EventArgs>> bar =
        Observable
            .FromEventPattern<EventHandler, EventArgs>(
                h => foo.Bar += h,
                h => foo.Bar -= h);

    var query =
        from ep in bar
        from name in Observable.FromAsync(() => GetNameAsync()) // async Task<string> GetNameAsync()
        from data in Observable.Start(() => LoadData(name)) // string LoadData(string name)
        select new { name, data };

    var subscription =
        query
            .Subscribe(x => Console.WriteLine($"{x.name} = {x.data}"));
}
Run Code Online (Sandbox Code Playgroud)

query美观简洁。

我很少使用标准的事件模型。我几乎总是使用 Observables。

添加 Observable 是因为它们曾经是并且仍然是比内置事件模型更强大的观察者模式抽象。