Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Covariance in generic interfaces

I wanted to create an observableCollection that is sortable so i started creating a class that inherit observable with some methods to sort it, then i wanted that class to persist the index into the childs, so i created an interface that expose an index property where i can write to, and i costrainted the T of my collection class to be of my Interface, then i wanted to be able from avery item to access the parentCollection and here the problems started because the type of the parent collection is generic ... i've tried many solutions, and i think covariance or invariance is the way, but i can't get it working ...

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

namespace ClassLibrary1
{
    public class SortableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>, ISortableCollection<T> where T : ISortable<T>
    {
        public void Sort()
        {
            //We all know how to sort something
            throw new NotImplementedException();
        }

        protected override void InsertItem(int index, T item)
        {
            item.Index = index;
            item.ParentCollection = this;
            base.InsertItem(index, item);
        }
    }

    public interface ISortableCollection<T> : IList<T>
    {
        void Sort();
    }

    public interface ISortable<T>
    {
        Int32 Index { get; set; }
        ISortableCollection<T> ParentCollection { get; set; }
    }

    public class BaseClass : ISortable<BaseClass>
    {
        public int Index { get; set; }

        public ISortableCollection<BaseClass> ParentCollection { get; set; }
    }

    public class DerivedClass : BaseClass { }

    public class Controller
    {
        SortableCollection<BaseClass> MyBaseSortableList = new SortableCollection<BaseClass>();
        SortableCollection<DerivedClass> MyDerivedSortableList = new SortableCollection<DerivedClass>();

        public Controller()
        {
            //do things
        }
    }
}

this is more or less the setup. I'd like to be able to create a SortableCollection<DerivedClass> but the types mismatch... wich is the correct way of doing it ?

exact error is

Error 1 The type 'ClassLibrary1.DerivedClass' cannot be used as type parameter 'T' in the generic type or method 'ClassLibrary1.SortableCollection<T>'. There is no implicit reference conversion from 'ClassLibrary1.DerivedClass' to 'ClassLibrary1.ISortable<ClassLibrary1.DerivedClass>'. c:\users\luigi.trabacchin\documents\visual studio 2013\Projects\ClassLibrary1\ClassLibrary1\Class1.cs 48 89 ClassLibrary1

like image 440
L.Trabacchin Avatar asked Apr 04 '15 15:04

L.Trabacchin


People also ask

What is generic covariance?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

Why is covariance and Contravariance important?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

What is generic interface?

A generic interface is primarily a normal interface like any other. It can be used to declare a variable but assigned the appropriate class. It can be returned from a method. It can be passed as argument. You pass a generic interface primarily the same way you would an interface.

What is covariance in programming?

Covariance means that a method can return a type that is derived from the delegate's return type. Contra-variance means that a method can take a parameter that is a base of the delegate's parameter type.


2 Answers

The problem is that your constraint on T is "T is required to be an I<T>", and you have passed a DerivedClass for T, but DerivedClass is not convertible to I<DerivedClass>, it is convertible to I<BaseClass>.

I don't know what you are trying to represent with the constraint that T be an I<T>. I do know that people often use this pattern to try to represent a constraint that the C# type system does not actually implement. See my article on the subject for details:

http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx

I encourage you to simplify things considerably; you seem to be trying to capture too much in the type system.

The reason why I<D> is not convertible to I<B> is because in order for variance to work, the interface must be marked as supporting variance; mark the T with out or in depending on whether you want covariance or contravariance.

However, since IList<T> is invariant, it will not be legal to make the derived interface covariant or contravariant. Consider IEnumerable<T> instead, as it is covariant in T.

In order for an interface to be covariant in T it needs to only use T in output positions. List<T> uses T in both input and output positions, so it cannot be covariant or contravariant.

like image 192
Eric Lippert Avatar answered Sep 20 '22 22:09

Eric Lippert


You need DerivedClass to be a ISortable<DerivedClass>:

public class DerivedClass : BaseClass, ISortable<DerivedClass>
{
    public new ISortableCollection<DerivedClass> ParentCollection
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
}

Co- and contravariance on T cannot work here because you are deriving from IList<T> which is invariant.

Even removing IList<T> and removing the getter I can't get it to work right now with variance. Not exactly a strength of mine. This is a part of the type system that is better left alone if you can help it.

If the type system makes your head explode consider a dynamic solution:

((dynamic))item).ParentCollection = this;
like image 42
usr Avatar answered Sep 17 '22 22:09

usr