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

问题描述

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

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

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

标签: c#design-patternsreactive-programming

解决方案


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

熟悉 FCL 中公开的类型的人会注意到,框架中不存在IObserverIObservableObservableImpl类型。

这是对的。我们推导出的IObservable<T>和接口作为和的数学对偶。它将集合从您同步请求值的东西转变为异步将值推送给您的东西。以下是Matthew Podwysocki关于二元性的说法:IObserver<T>IEnumerable<T>IEnumerator<T>

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

回到你引用的文本。

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

这对我来说似乎完全倒退了。自 v1.0 以来,代表和事件一直在框架中。如果他们制造IObservable<T>/IObserver<T>过时,则无需介绍它们(这是您问题的症结所在)。

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

事实上,由于委托和事件是 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());
    }
}

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

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

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

Observables 并非如此。

这段代码编译得很好:

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());
    }
}

Observables 是 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());
    }
}

运行时它仍然会产生“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());
    }
}

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());
    }
}

不仅可以传递事件(可观察),而且可以传递分离处理程序的能力,而无需参考原始处理程序本身。在我的示例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}"));
}

query是美丽而简洁的。

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

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


推荐阅读