Why generic IList<> does not inherit non-generic IList
Asked Answered
N

4

16

IList<T> does not inherit IList where IEnumerable<out T> inherits IEnumerable.

If out modifier are the only reason then why most of the implementation of IList<T> (e.g. Collection<T>, List<T>) implements IList interface.

So any one can say OK, if that statements is true for all implementation of IList<T> then directly cast it to IList when necessary. But problem is that though IList<T> does not inherit IList so it is not guaranteed that every IList<T> object are IList.

Moreover using IList<object> is obviously not the solution because without out modifier generics can not be assigned to a less inherit class; and creating new instance of List is not a solution here because someone may want actual reference of the IList<T> as an IList pointer; and use List<T> insteed of IList<T> is actually a bad programming practice and doesn't serve all purpose.

If .NET wants to give flexibility that every implementation of IList<T> should not have a contract of non-generic implementation (i.e. IList) then why they didn't keep another interface which implement both generic and non-generic version and didn't suggest that all concrete class which want to contract for generic and non-genetic item should contract via that interface.

Same problem occurs for casting ICollection<T> to ICollection and IDictionary<TKey, TValue> to IDictionary.

Neukam answered 28/1, 2013 at 9:33 Comment(10)
See blogs.msdn.com/b/brada/archive/2005/01/18/355755.aspxHengelo
@JonSkeet I think he has an error in the article. Ienumerable is Covariant and not Contravariant. . search for this exact phrase in the article : "As it turns out, the only generic interface for which this is possible is IEnumerable<T>, because only IEnumerable<T> is contra-variant:"Hornbeam
Do not worry about your difficulties in Mathematics. I can assure you mine are still greater. Albert Einstein, for your guide :DLibbie
@RoyiNamir: Yes, I think you're right - but the rest of the article is still appropriate.Hengelo
@Nafeez Abrar: what's you actual problem you need to solve?Lynettelynn
@CodesInChaos: so tell me how you would call the "Add" method when you don't know the generic type at compile time, without using the "legacy interface"?Lynettelynn
@StefanSteinegger If I wanted to forgo the safety of static typing, then I'd go with dynamic or reflection. But that's pretty rare in my experience.Shelbyshelden
@CodesInChaos: reflection gets really nasty when dealing with generics. Just to call "Add" on a collection it is overkill. But you are right, its a very rare case. Anyway I enjoy having a nice interface independent of how much type information I have at compile time. The problem is not that non-generic is obsolete, the problem is that Microsoft didn't manage to create useful and consistent collection interfaces.Lynettelynn
@JonSkeet the link is now broken, as many other great blog posts in MSDN, as I found recently. Any chance they've been moved elsewhere known? Anyway, for this one specifically I found a copy in the Wayback Machine: web.archive.org/web/20140417104304/http://blogs.msdn.com/b/…Heffernan
@alelom: I don't know of anywhere, I'm afraid.Hengelo
P
9

As you note, T in IList<T> is not covariant. As a rule of thumb: any class that can modify its state cannot be covariant. The reason is that such classes often have methods that have T as the type of one of their parameters, e.g. void Add(T element). And covariant type parameters are not allowed in input positions.

Generics were added, among other reasons, to provide type safety. For example, you can't add an Elephant to a list of Apple. If ICollection<T> were to extend ICollection, then you could call ((ICollection)myApples).Add(someElephant) without a compile-time error, as ICollection has a method void Add(object obj), which seemingly allows you to add any object to the list, while in practice you can only add objects of T. Therefore, ICollection<T> does not extend ICollection and IList<T> does not extend IList.

Anders Hejlsberg, one of the creators of C#, explains it like this:

Ideally all of the generic collection interfaces (e.g. ICollection<T>, IList<T>) would inherit from their non-generic counterparts such that generic interface instances could be used both with generic and non-generic code.

As it turns out, the only generic interface for which this is possible is IEnumerable<T>, because only IEnumerable<T> is contra-variant [sic1]: In IEnumerable<T>, the type parameter T is used only in "output" positions (return values) and not in "input" positions (parameters). ICollection<T> and IList<T> use T in both input and output positions, and those interfaces are therefore invariant.

1) IEnumerable<T> is co-variant


Since .Net 4.5 there are the IReadOnlyCollection<out T> and IReadOnlyList<out T> covariant interfaces. But IList<T>, ICollection<T> and many of the list and collection classes don't implement or extend them. Frankly, I find them not very useful, as they only define Count and this[int index].


If I could redesign .Net 4.5 from the ground up, I would have split the list interface into a read-only covariant interface IList<out T> that includes Contains and IndexOf, and a mutable invariant interface IMutableList<T>. Then you could cast IList<Apple> to IList<object>. I implemented this here:

M42 Collections - Covariant collections, lists and arrays.

Prentiss answered 18/1, 2014 at 11:34 Comment(2)
Any implementation of IList<T> could legitimately implement IList by saying having IList.IsReadOnly return true even if IList<T>.IsReadOnly is false. Note that String[] implements both IList<String> and IList<Object>; the IsReadOnly property of the former will return false, but the IsReadOnly property of the latter will return true, so the idea of a collection which implements several "maybe-writable" interfaces and is writable by some but not all is hardly unknown.Mcreynolds
Your answer is correct, but the first linked/quoted description is oddly incorrect. Specifically, IEnumerable is Covariant, not Contra-variant. See this answer for reference. I'd suggest calling this out to avoid more confusion.Herrmann
P
3

Note that since 2012, in .NET 4.5 and later, there exists a covariant (out modifier) interface,

public interface IReadOnlyList<out T>

see its documentation.

Usual collection types like List<YourClass>, Collection<YourClass> and YourClass[] do implement IReadOnlyList<YourClass> and because of the covariance can also be used as IReadOnlyList<SomeBaseClass> and ultimately IReadOnlyList<object>.

As you have guessed, you will not be able to modify your list through a IReadOnlyList<> reference.

With this new interface, you might be able to avoid the non-generic IList all together. However you will still have the problem that IReadOnlyList<T> is not a base interface of IList<T>.

Phantasmagoria answered 18/1, 2014 at 11:12 Comment(1)
Actually, covariance and contravariance in generics are supported since .NET 4.0. Only the read-only interfaces like IReadOnlyList<out T> were added in .NET 4.5.Findlay
T
1

Create an interface MyIList<T> and let it inherit from IList<T> and IList:

public interface MyIList<T> : IList<T>, IList
{ }

Now create a class MySimpleList and let it implement MyIList<T>:

public class MySimpleList<T> : MyIList<T>
{
    public int Count
    {
        get { throw new NotImplementedException(); }
    }

    public bool IsFixedSize
    {
        get { throw new NotImplementedException(); }
    }

    public bool IsReadOnly
    {
        get { throw new NotImplementedException(); }
    }

    public bool IsSynchronized
    {
        get { throw new NotImplementedException(); }
    }

    public object SyncRoot
    {
        get { throw new NotImplementedException(); }
    }

    object IList.this[int index]
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public T this[int index]
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public void Add(T item)
    {
        throw new NotImplementedException();
    }

    public int Add(object value)
    {
        throw new NotImplementedException();
    }

    public void Clear()
    {
        throw new NotImplementedException();
    }

    public bool Contains(T item)
    {
        throw new NotImplementedException();
    }

    public bool Contains(object value)
    {
        throw new NotImplementedException();
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public void CopyTo(Array array, int index)
    {
        throw new NotImplementedException();
    }

    public IEnumerator<T> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public int IndexOf(T item)
    {
        throw new NotImplementedException();
    }

    public int IndexOf(object value)
    {
        throw new NotImplementedException();
    }

    public void Insert(int index, T item)
    {
        throw new NotImplementedException();
    }

    public void Insert(int index, object value)
    {
        throw new NotImplementedException();
    }

    public bool Remove(T item)
    {
        throw new NotImplementedException();
    }

    public void Remove(object value)
    {
        throw new NotImplementedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }
}

What you can easily see now, is that you have to double implement a bunch of methods. One for the type T and one for object. In normal circumstances you want to avoid this. This is a problem of co-variance and contra-variance.

The best explanation you can find (for this concrete problem with IList and IList is the article from Brad already mentioned by Jon within the comments of the question.

Tondatone answered 28/1, 2013 at 9:45 Comment(4)
And many of the IList methods will need a if(!(x is T)) throw ... check. Makes implementing IList even more annoying.Shelbyshelden
@CodesInChaos: Yes, that's one of the points that will encounter if you're going to implement this class.Tondatone
@Tondatone Actual problem is casting an System.Collection.Generic.IList<T> instance to a System.Collection.IList pointer. So writing another interface can't be a good solution of this problem. To use this solution one have to use MyIList<T> and write individual wrappers for every concrete class which inherits IList<T> and IList like class ListWrapper : List, MyIList {} and constructors if necessary. But It would be more easier if .NET List<> (and other classes which actually inherit both IList<> and IList) inherits an interface like MyIList.Neukam
I don't want to say, you have should use your own interface. It simply illustrates, why IList<T> does not inherit from IList. If you try to do so on yourself (like in my example) then you'll see that you run into a lot of problems and that's the reason why Microsoft also didn't do so and leaved the interfaces separately.Tondatone
R
0

Good answers have already been given. A notice about IList though:

MSDN IList Remarks: "IList implementations fall into three categories: read-only, fixed-size, and variable-size. (...). For the generic version of this interface, see System.Collections.Generic.IList<T>."

This is a bit misleading because on the generic side, we have IList<T> as variable-size, and IReadOnlyList<T> as read-only since 4.5 but AFAIK, there is no fixed-size generic List.

Roseberry answered 9/11, 2016 at 9:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.