4

List implements IList so I expect IList will accept a List object but why IList> doesn't accept List>?

static IList<int> List_1()
    {
        List<int> list = new List<int> { 1,2,3,3,4,5};

        return list;
    }

    static IList<IList<int>> List_2()
    {
        List<List<int>> parent = new List<List<int>>();
        List<int> list = new List<int> { 1, 2, 3, 3, 4, 5 };
        parent.Add(list);

        return parent; //compiler error CS0266
    }
Ronald Abellano
  • 774
  • 10
  • 34
  • 2
    Do you understand why `List` doesn't accept `List`? – Sweeper May 17 '19 at 05:33
  • 5
    return (IList>)parent; – jdweng May 17 '19 at 05:34
  • 2
    @jdweng: That won't work, you'll get a runtime exception. – Wiktor Zychla May 17 '19 at 05:37
  • Possible duplicate of [Why can't I cast from a List to List?](https://stackoverflow.com/questions/5881677/why-cant-i-cast-from-a-listmyclass-to-listobject) – SᴇM May 17 '19 at 05:46
  • 1
    @SᴇM It is not about casting one type to an object type it is about IList and List. – vsarunov May 17 '19 at 05:47
  • Possible duplicate of https://stackoverflow.com/questions/26501312/cannot-implicitly-convert-type-system-collections-generic-list-to-system-c – Shad May 17 '19 at 05:47
  • @vsarunov You know right, that compiler gives an error, because it cannot cast from `List>` to `IList>`? – SᴇM May 17 '19 at 05:56
  • @SᴇM So does that have to do with casting List to List ? If I had this problem It would be simple a linq statement or using just "as" or "is" and checking if you can cast it. It is specifically IList and List. – vsarunov May 17 '19 at 06:00
  • @vsarunov Yes it does, cause `MyClass` derived from `object`, as `List` derived from `IList`. So if you have for example method which returns `List`, you cannot return `List` without casting. – SᴇM May 17 '19 at 06:05

5 Answers5

3

That's because of

List<T> implements IList<T> but

List<List<T>> does not implement IList<IList<int>>

That's why your first method works as intended and second not.

Just change your declaration of the list in the second method to

List<IList<int>> parent = new List<IList<int>>();

And this is the case of covariance and contravariance.

Generic type parameters support covariance and contravariance but you need to define in that way

By learn.microsoft.com

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

er-sho
  • 9,581
  • 2
  • 13
  • 26
1

Suppose this works. Your client code is:

var result = List_2();

Since the contract allows adding to the result anything that's IList<int>, you could possibly have

public class MyCustomIList : IList<int>
{
    ...
}

and then

var result = List_2();
result.Add( new MyCustomIList() );

But that's wrong!

Your result is a list of List<int>, you should not be allowed to add anything other than List<int> or its derivatives there. However, you were able to add MyCustomIList which is not related to the List<int>.

If you need a broad picture of the issue, read more on covariance and contravariance.

The fundamental issue in this particular example comes from the Add operation. If you don't need it, the IEnumerable will do

static IEnumerable<IEnumerable<int>> List_2()
{
    List<List<int>> parent = new List<List<int>>();
    List<int> list = new List<int> { 1, 2, 3, 3, 4, 5 };
    parent.Add(list);

    return parent; // no error, this works
}

This has been covered already.

Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
0

Why then does List implement IList?

It is a bit odd, since List for any type other than object does not fulfill the full contract of IList. It's probably to make it easier on people who are updating old C# 1.0 code to use generics; those people were probably already ensuring that only the right types got into their lists. And most of the time when you're passing an IList around, it is so the callee can get by-index access to the list, not so that it can add new items of arbitrary type.

I would suggeste return IEnumerable instead of IList, will simplify your life, since List Fully implements it.

vsarunov
  • 1,433
  • 14
  • 26
0

I don't know why you want to return exactly IList<IList<int>>, but one way of doing that is to use Cast<T>() method:

static IList<IList<int>> List_2()
{
    List<List<int>> parent = new List<List<int>>();
    List<int> list = new List<int> { 1, 2, 3, 3, 4, 5 };
    parent.Add(list);

    return parent.Cast<IList<int>>().ToList();
}

Or ConvertAll() method:

return parent.ConvertAll(x => (IList<int>)x);

Both methods will run over all elemets, and cast/convert them to a given type, so I think it would be better to return IList<List<int>> instead (if that's possible).

SᴇM
  • 7,024
  • 3
  • 24
  • 41
0

The problem is with your method return type. Modify your method signature to return to IList<List<int>> rather than returning IList<IList<int>>

static IList<List<int>> List_2()
    {
        List<List<int>> parent = new List<List<int>>();
        List<int> list = new List<int> { 1, 2, 3, 3, 4, 5 };
        parent.Add(list);

        return parent; //no compiler error
    }

Now it will work fine as your method now returns an IList of List<int>

Voodoo
  • 1,550
  • 1
  • 9
  • 19