协变性比多态性更酷,而且不会重复吗?

9

.NET 4 引入了协变(covariance)。我猜它很有用。毕竟,微软花了很多功夫将其添加到 C# 语言中。但是,为什么协变比传统的多态(polymorphism)更有用呢?

我写了这个例子来理解为什么我应该实现协变,但我还是不明白。请给我指点迷津。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sample
{
    class Demo
    {
        public delegate void ContraAction<in T>(T a);

        public interface IContainer<out T>
        {
            T GetItem();
            void Do(ContraAction<T> action);
        }

        public class Container<T> : IContainer<T>
        {
            private T item;

            public Container(T item)
            {
                this.item = item;
            }

            public T GetItem()
            {
                return item;
            }

            public void Do(ContraAction<T> action)
            {
                action(item);
            }
        }

        public class Shape
        {
            public void Draw()
            {
                Console.WriteLine("Shape Drawn");
            }
        }

        public class Circle:Shape
        {
            public void DrawCircle()
            {
                Console.WriteLine("Circle Drawn");
            }
        }

        public static void Main()
        {
            Circle circle = new Circle();
            IContainer<Shape> container = new Container<Circle>(circle);
            container.Do(s => s.Draw());//calls shape

            //Old school polymorphism...how is this not the same thing?
            Shape shape = new Circle();
            shape.Draw();
        }
    }
}

2
你应该阅读这篇文章:https://dev59.com/VHNA5IYBdhLWcg3wIqPr - rsenna
看起来你真正想问的是协变性,而不是逆变性? - Dan Tao
同意,丹。我很想编辑这个问题,但我会让布莱恩澄清。 - Adam Rackis
@Dan 是的,我在两者之间犹豫不决,然后迷失了方向。谢谢你指出来。 - P.Brian.Mackey
3个回答

10

考虑一个请求 IContainer<Shape> 的 API:

public void DrawShape(IContainer<Shape> container>) { /* ... */ }

您有一个Container<Circle>。如何将您的容器传递给DrawShape API?如果没有协变性,类型Container<Circle>不能转换为IContainer<Shape>,需要重新包装该类型或提出其他解决方法。

这在使用许多泛型参数的API中并不罕见。


5
协变性比多态性更酷,就像野兔比溜冰鞋更酷:它们不是同一件事。
协变性和逆变性(以及不变性和...全变性...有人吗?)处理泛型相对于继承可以走的“方向”。在您的例子中,您正在做相同的事情,但这不是有意义的示例。
例如,考虑到 IEnumerable<T>out T。这让我们能够做这样的事情:
public void PrintToString(IEnumerable<object> things)
{
    foreach(var obj in things)
    {
        Console.WriteLine(obj.ToString());
    }
}

public static void Main()
{
    List<string> strings = new List<string>() { "one", "two", "three" };
    List<MyClass> myClasses = new List<MyClass>();

    // add elements to myClasses

    PrintToString(strings);
    PrintToString(myClasses);
}

在之前的C#版本中,这是不可能的,因为List<string>实现了IEnumerableIEnumerable<string>,而不是IEnumerable<object>。然而,由于IEnumerable<T>out T,我们知道它现在可以与任何IEnumerable<Y>进行赋值或参数传递,其中T是YT:Y

在一些情况下,之前的版本可以通过使函数本身成为通用函数并使用通用类型推断来解决这种情况,从而在许多情况下产生相同的语法。然而,这并没有解决更大的问题,也不是一种100%的解决方法。


-1

这是泛型版本的:

object[] arr = new string[5];

我认为是否真正需要这个是一个观点问题,因为它可能会引入错误,就像说话时发生的那些错误一样:
arr[0] = new object(); //Run-time error

但有时它非常方便,因为它可以更好地重用代码。


编辑:

我忘了——如果你正确使用outin关键字,你可以防止这些错误。所以并没有太大的不利之处。


实际上,添加逆变的目的是为了防止您所提到的那些错误。 - JSBձոգչ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接