代码之家  ›  专栏  ›  技术社区  ›  DoubleTrouble

当类型从同一接口派生时合并可观察序列

  •  3
  • DoubleTrouble  · 技术社区  · 6 年前

    我想合并两个可观测序列。序列由代码示例中ex1中的消息组成。当ex2按预期编译和运行时,我不明白为什么它不编译。

    有人能给我解释一下吗?

    class Message<T> where T : IFoo {}
    interface IFoo {}
    class Foo : IFoo {}
    class Bar : IFoo {}
    
    class Program
    {
        static void Ex1()
        {
            var o1 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Message<Foo>());
            var o2 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Message<Bar>());
    
            o1.Merge<Message<IFoo>>(o2).Subscribe(Console.WriteLine);
            Console.ReadKey();
        }
    
        static void Ex2()
        {
            var o1 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Foo());
            var o2 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Bar());
    
            o1.Merge<IFoo>(o2).Subscribe(Console.WriteLine);
            Console.ReadKey();
        }
    }
    

    Visual Studio 2017中.NET Framework 4.6.1的错误:

    Error   CS1929  'IObservable<Message<Foo>>' does not contain a definition for 'Merge' and the best extension method overload 'Observable.Merge<Message<IFoo>>(IObservable<IObservable<Message<IFoo>>>, int)' requires a receiver of type 'IObservable<IObservable<Message<IFoo>>>'
    

    多谢提前!

    3 回复  |  直到 6 年前
        1
  •  1
  •   Enigmativity    6 年前

    只是因为 Foo 是从 IFoo 这并不意味着 Message<Foo> 来源于 Message<IFoo> - 它不 -所以你不能像以前那样投。同样适用于 Bar IBar .

    让我们看看原因。

    而不是思考 Message<T> 让我们使用 List<T> . 我现在有了这个密码:

    var foos = new List<Foo>();
    
    foos.Add(new Foo());
    foos.Add(new Foo());
    

    那很好。

    现在,就像你的问题,当你试图改变 消息<foo> 变成一个 消息<ifoo> ,我们尝试将此更改为 List<IFoo> :

    List<IFoo> ifoos = (List<IFoo>)foos; // Does not compile
    

    假设它确实进行了编译—为了解释为什么您的代码不能工作—所以我将 ifoos 我可以这样做:

    ifoos.Add(new Bar());
    

    毕竟,我的参考资料 IFOOS 属于类型 列表<ifoo> 酒吧 来源于 IFOO 所以这将是完全有效的代码。

    但是 IFOOS 只是一个演员 List<Foo> 并且转换对象不会更改其基础类型。一 列表<foo> 只能接受类型为的元素 (或源自 )现在,考虑到我们对编译器的神奇更改,我们已经创建了一个它可以接受的情况 酒吧 .

    繁荣!运行时错误。

    这就是为什么演员 (List<IFoo>)foos 会失败的。所以演员们 (Message<IFoo>)Message<Foo> 你的问题也失败了。

        2
  •  1
  •   Ciaran_McCarthy    6 年前

    我们将从第二种方法的工作原理开始。

    第二个方法创建两个类型的集合 Foo Bar . 但在 Merge 方法要使用的类型显式为接口 IFoo . 这告诉方法这是要存储在新集合中的对象类型: IEnumerable<IFoo> . 集合接受类型为的项 IFOO 以及以后 酒吧 两者都继承自集合接受的接口。如果要从 合并 方法,或显式指定 酒吧 作为类型,您将具有与第一个方法相同的错误。

    那么第一种方法呢?

    第一个集合的类型为 Message<Foo> . 此的泛型类型 Message . 注意,这与 Message<IFoo> ,因为类型是显式的 ,而不是它的超类或接口。同样地 Message<Bar> 具有泛型类型 酒吧 不是 IFOO .

    当编译器到达 合并 具有两个集合的方法(其中一个 消息<foo> 消息<栏> )它知道 Messages 不一样(a 是一个 IFOO 和一个 酒吧 是一个 IFOO 但A 不是一个 酒吧 ,反之亦然)。所以编译器给出一个错误,说它需要一个相关类型的集合( requires a receiver of type 'IObservable<IObservable<Message<IFoo>>>' )

    还有最后一件事要注意。

    的定义 消息 类是 class Message<T> where T : IFoo {} . 所有的 where 条款意思是指 T 必须是 IFOO 就这样。这并不意味着 消息<foo> 是一个 消息<ifoo> . 你可以从一个 IFOO 第二种方法就是这样,但它们不一样。

        3
  •  1
  •   Sentinel    6 年前

    除了这里的其他答案:

    interface IMessage<out T> where T : IFoo { }
    class Message<T> : IMessage<T> where T:IFoo { }
    interface IFoo { }
    class Foo : IFoo { }
    class Bar : IFoo { }
    
    class Program
    {
        static void Ex1()
        {
            var o1 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => (IMessage<IFoo>)new Message<Foo>());
            var o2 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => (IMessage<IFoo>)new Message<Bar>());
    
            o1.Merge(o2).Subscribe(Console.WriteLine);
            Console.ReadKey();
        }
    
        static void Main(string[] args)
        {
            Ex1();
        }
    }