Reputation: 25419
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.
Upvotes: 5
Views: 513
Reputation: 25419
I've figured out a method to work through my interface conversions. Here's the situation again, falling back to my example of a List of Dictionaries. (Could be any data types -- built in or custom though).
class Program
{
private static List<Dictionary<string, Guid>> GetList()
{
var myList = new List<Dictionary<string, Guid>>();
for (var i = 0; i < 25; i++)
{
var newDict = new Dictionary<string, Guid>();
for (var j = 0; j < 25; j++)
{
newDict.Add(j.ToString(), Guid.NewGuid());
}
myList.Add(newDict);
}
return myList;
}
//Really, I want the following to use interfaces; lets not worry about why there is a middle-man function.
private static IList<Dictionary<string, Guid>> MyIntermediateFuncForAReason()
{
var originalList = GetList();
//whatever processing.
return originalList;
}
static void Main(string[] args)
{
var t = MyIntermediateFuncForAReason();
// whatever...
}
}
Modify the MyIntermediateFuncForAReason
to convert the to the desired types such as:
private static IList<IDictionary<string, Guid>> MyIntermediateFuncForAReason()
{
var originalList = GetList();
//whatever processing.
return originalList.Select((IDictionary<string, Guid> i) => i).ToList();
}
Upvotes: 0
Reputation: 43046
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:
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.
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
Upvotes: 1
Reputation: 203829
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.
Upvotes: 5
Reputation: 56536
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();
}
Upvotes: 5