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

时间:2019-02-16 21:53:20

标签: c# design-patterns reactive-programming

我只想知道何时使用它们以及它们的优点 我真的很难理解为什么.net在拥有事件委托后才引入IObservable / IObserver 并据此首选MSDN事件委托

  

基于对Observer模式的理解,现在让我们将注意力转向在.NET Framework中使用此模式。那些熟悉FCL中公开的类型的人会注意到,框架中不存在IObserver,IObservable或ObservableImpl类型*。缺少它们的主要原因是CLR使它们过时而过时。尽管您当然可以在.NET应用程序中使用这些构造,但是* delegates和event的引入为实现Observer模式提供了一种新的强大功能,而无需开发专用于支持该模式的特定类型。实际上,由于委托和事件是CLR的一等成员,因此该模式的基础已并入.NET Framework的核心。因此,FCL在其整个结构中都广泛使用了Observer模式。

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

1 个答案:

答案 0 :(得分:4)

我在MSDN网站上找到了对引用文字的引用。我不得不说我很震惊。对于IObservable<T>的实现,这似乎是一个错误的,错误的看法。

  
    

那些熟悉FCL中公开的类型的人会注意到,框架中没有IObserverIObservableObservableImpl类型。

  

这是正确的。 IObservable<T>IObserver<T>接口是IEnumerable<T>IEnumerator<T>的数学对偶。它将集合从您同步请求值转变为异步将值推送给您的集合。 Matthew Podwysocki说了对偶性:

  

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

返回引用的文本。

  
    

缺少它们的主要原因是CLR使它们过时了而过时了。尽管您当然可以在.NET应用程序中使用这些构造,但是委托和事件的引入为实现Observer模式提供了一种新的强大功能,而无需开发专用于支持该模式的特定类型。

  

这对我来说似乎完全是倒退。自v1.0以来,代表和事件一直在框架中。如果它们使IObservable<T> / IObserver<T>过时,那么就不需要引入它们了(这是您所要解决的问题)。

我当时的记忆是,Microsoft对这对接口的价值深信不疑,因此他们急于将它们包含在BCL中,以便开发人员可以在完整的System.Reactive实施之前编写自己的基本代码。被释放。

  
    

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

  

这再次是对“一流成员”的含义的关注。 Wikipedia说:

  

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

活动肯定不是一流的公民。您不能传递事件,也不能独立于声明该事件的类引发事件。

以这个简单的例子为例:

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

可观察对象是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。

添加了可观察对象,因为它们比过去的事件模型仍然强大得多,是对观察者模式的抽象。