Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conversion of Generic using Interfaces

Tags:

c#

generics

I'm dealing with a code base that makes generous use of Generics with class types. I've been pushing to use interfaces (for a variety of reasons). In my work of slowly converting things, I've hit the following and I can't get past it.

Put simply (and minimally), the problem I'm facing is the following:

private static IList<IDictionary<int, string>> exampleFunc()
{
    //The following produces a compile error of:
    // Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.Dictionary<int,string>>' to 
    //  'System.Collections.Generic.IList<System.Collections.Generic.IDictionary<int,string>>'. 
    // An explicit conversion exists (are you missing a cast?)  
    return new List<Dictionary<int, string>>(); 
}

As it says in the comment, it produces a compile time error.

How can I produce an object that adheres to the interface that can be returned?

In general, how can I convert or cast the "inner" type?


Update: My example may be misleading. My situation is not specific to a List that contains Dictionary elements. There are instances of that but it could also just as well be IList<IVehicle> vehicleList = new List<Car>();

The crux of my question is how to deal with mis-matched Generic Types.

like image 234
Frank V Avatar asked Jun 05 '14 20:06

Frank V


People also ask

What is a 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.

Can a generic implement an interface?

Only generic classes can implement generic interfaces. Normal classes can't implement generic interfaces.

Can interfaces be generic in Java?

Java Generic Interface In similar way, we can create generic interfaces in java. We can also have multiple type parameters as in Map interface. Again we can provide parameterized value to a parameterized type also, for example new HashMap<String, List<String>>(); is valid.


3 Answers

You're trying to treat an IList as if it were covariant, meaning that you want to be able to treat all instances of the generic argument as if they are in fact of a less derived type. That won't work because IList is invariant. (Imagine what would happen if someone added a SomeOtherTypeOfDictionary<int,string> to the list that you return. You've now put something that's not a Dictionary<int,string> into a List<Dictionary<int, string>>.

You could return an IEnumerable<Dictionary<int, string>> which is covariant.

Or, if you don't actually have an already populated list, just create a list of the appropriate type (List<IDictionary<int, string>>) from the start.

Another option is to create a new list and copy over all of the elements in it return new List<Dictionary<int, string>>().Cast<IDictionary<int, string>>>().ToList();, although this usually isn't good design, particularly of you haven't already populated the list with data.

And all of this assumes that you have a real compelling reason to have an IList<IDictionary<int,string>> to begin with. I'd assert that there really isn't a compelling reason to restrict your List and Dictionary to the corresponding interfaces in the first place, and that there's nothing wrong with returning a List<Dictionary<int,string>> unless you really do have use cases for returning non-List ILists or non-Dictionary IDictionaries. It complicates your code, removes valuable functionality for the caller, and doesn't really add value if you aren't going to ever return different implementations of that interface.

like image 160
Servy Avatar answered Oct 30 '22 01:10

Servy


Your code won't work because IList<T> is not covariant/contravariant.

If you create the list yourself, you can give it a compatible type to start with:

private static IList<IDictionary<int, string>> exampleFunc()
{
    var list = new List<IDictionary<int, string>>();
    list.Add(new Dictionary<int, string>());
    return list;
}

If that's not an option, e.g. because you get it like that from an external source, you can cast it.

private static IList<IDictionary<int, string>> exampleFunc()
{
    var list = new List<Dictionary<int, string>>();
    list.Add(new Dictionary<int, string>());
    return list.Cast<IDictionary<int, string>>().ToList();
}
like image 26
Tim S. Avatar answered Oct 29 '22 23:10

Tim S.


List<T> implements IList<T>, as we all know. Therefore, we can write this:

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

Now assume an interface I and a class C, where C implements I. We can write this:

I x = new C();

It is a common mistake to combine these two facts into a belief that this should be possible:

IList<I> xs = new List<C>();

Why is this illegal? Two reasons:

First, List<C> does not implement IList<I>. It implements IList<C>.

Second, treating a List<C> as an IList<I> breaks type safety. For example, consider class D, which also implements interface I. If we could assign a List<C> to IList<I>, then we could do this:

IList<C> cs = new List<C>();
IList<I> eyes = cs;  //hypothetically legal for this thought experiment
eyes.Add(new D());   //legal because D implements I
C c = cs[0];         //legal, but results in a C variable pointing to a D object!

To prevent this, the compiler disallows the second line of code.

If this were legal, then the List<T>.Add(T) method would have to check the type of its argument, which negates one of the primary benefits of the generic system in the first place.

The solution to your problem is to use concrete collection types instantiated with interface type arguments:

private static IList<I> exampleFunc()
{
    return new List<I>();
}

If you're getting the list from elsewhere, you have two choices:

  1. Write a wrapper class that implements IList<I> but holds a reference to the IList<C>, casting objects (possibly raising exceptions) before adding them to the inner list.

  2. Copy the data into a List<I>.

The only reason to use the first solution, which is a bit cumbersome, is if you need changes to the list to be reflected in some other part of the application that has a reference to the original List<C>.

As other posters have mentioned, this question is related to the concept of generic type parameter covariance and contravariance, which allows you to write IEnumerable<I> xs = new List<C>(); because IEnumerable<T> is covariant in T. If you want to understand this better, and why List<T> can't be covariant in T, you might want to start with Eric Lippert's excellent series on the subject, starting with http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx

like image 1
phoog Avatar answered Oct 30 '22 01:10

phoog