In a generic C# class for an internal reusable library, I'd like to pass a reference to "something that maps to a list of other things". The data types of what is passed in there are not supposed to be known by the library. Also, the way they are stored should not be known either, i.e. what is today a list that is held in memory, might later be a database table that is read from on demand.
So I thought I'd write this library class:
class GenericClass<T, U>
{
public void Foo(IDictionary<T, IEnumerable<U>> bar)
{
// do something
}
}
This compiles, but trying to pass in concrete implementations does not:
class UsingClass
{
public static void Main(string[] args)
{
var c = new GenericClass<string, string>();
c.Foo(new Dictionary<string, List<string>>());
}
}
I'm getting the following two syntax errors:
Filename.cs(46,13): error CS1502: The best overloaded method match for 'GenericClass<string,string>.Foo(System.Collections.Generic.IDictionary<string,System.Collections.Generic.IEnumerable<string>>)' has some invalid arguments
Filename.cs(46,19): error CS1503: Argument 1: cannot convert from 'System.Collections.Generic.Dictionary<string,System.Collections.Generic.List<string>>' to 'System.Collections.Generic.IDictionary<string,System.Collections.Generic.IEnumerable<string>>'
Replacing the IEnumerable
on the declaration of Foo()
with List
fixes it, but that's of course not quite what I want.
Is this really not supported by C# (4.0) or am I just missing something obvious? What workaround would you suggest? (I'm sure this has been talked about before a lot, so links to great descriptions are fine, too.)
Yes, I should be able to write my own helper classes for that, but why do I have to?
Yes, this is really not supported. Imagine your Foo method looked like this:
public void Foo(IDictionary<T, IEnumerable<U>> bar)
{
T key = GetKeyFromSomewhere();
bar[key] = new U[10]; // Create an array
}
That looks okay, doesn't it? We can convert from U[]
to IEnumerable<U>
.
It's not so good from the caller's point of view though - suddenly we've got a string[]
reference value in the dictionary, when all the values are meant to be List<string>
references! Bang goes type safety.
You can rewrite the method as:
public void Foo<TValue>(IDictionary<T, TValue> bar)
where TValue : IEnumerable<U>
That will let you get values out of the dictionary and convert them to IEnumerable<U>
implicitly... but you'd only be able to put exactly the right type of value into the dictionary, and you can't build that just from a U
value.
As of version 4, C# supports generic variance in restricted circumstances. So for example, this works in C# 4 (when targeting .NET 4) but previously wouldn't:
List<string> strings = new List<string>();
IEnumerable<object> objects = strings;
For a lot more on generic variance, see Eric Lippert's blog series on the topic. Be prepared for your brain to explode periodically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With