Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is covariance cooler than polymorphism...and not redundant?

.NET 4 introduces covariance. I guess it is useful. After all, MS went through all the trouble of adding it to the C# language. But, why is Covariance more useful than good old polymorphism?

I wrote this example to understand why I should implement Covariance, but I still don't get it. Please enlighten me.

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();
        }
    }
}
like image 450
P.Brian.Mackey Avatar asked Jan 04 '11 19:01

P.Brian.Mackey


2 Answers

Consider an API which asks for an IContainer<Shape>:

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

You have a Container<Circle>. How can you pass your container to the DrawShape API? Without covariance, the type Container<Circle> is not convertible to IContainer<Shape>, requiring you to rewrap the type or come up with some other workaround.

This is not an uncommon problem in APIs that use a lot of generic parameters.

like image 141
JSBձոգչ Avatar answered Oct 25 '22 08:10

JSBձոգչ


Covariance is cooler than polymorphism in the same way that jackrabbits are cooler than iceskates: they're not the same thing.

Covariance and contravariance (and invariance and...omnivariance...anybody?) deal with the "direction" that generics can go with regard to inheritance. In your example, you're doing the same thing, but that's not a meaningful example.

Consider, for example, the fact that IEnumerable<T> is out T. This lets us do something like this:

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

In previous versions of C#, this would have been impossible, as List<string> implements IEnumerable and IEnumerable<string>, not IEnumerable<object>. However, since IEnumerable<T> is out T, we know that it's now compatible for assignment or parameter passing for any IEnumerable<Y> where T is Y or T:Y.

This sort of thing could be worked around in previous versions under some circumstances by making the function itself generic and using generic type inference, yielding identical syntax in many cases. This, however, did not solve the larger problem and was by no means a 100% workaround.

like image 40
Adam Robinson Avatar answered Oct 25 '22 06:10

Adam Robinson