9

Possible Duplicate:
In C#, why can't a List<string> object be stored in a List<object> variable

Why doesn't the below work?

List<string> castMe = new List<string>();
IEnumerable<string> getFromCast  = (IEnumerable<string>)castMe; // allowed.

Dictionary<int, List<string>> castMeDict = new Dictionary<int, List<string>>();
Dictionary<int, IEnumerable<string>> getFromDict = (Dictionary<int, IEnumerable<string>>)castMeDict;  // Not allowed

Is this a flaw in the Dictionary casting mechanism, or in my thinking that this should be allowed?

Thanks.

Community
  • 1
  • 1
user420667
  • 6,552
  • 15
  • 51
  • 83
  • 5
    See: [Understanding Covariant and Contravariant interfaces in C#](http://stackoverflow.com/questions/2719954/understanding-covariant-and-contravariant-interfaces-in-c-sharp) – Magnus Dec 19 '11 at 20:39
  • Thanks everybody. Sorry, I did check for duplicates but it's a bit difficult to first generalize, then search for all specific instances that may be duplicates, without first knowing the answer. – user420667 Dec 19 '11 at 21:36

3 Answers3

24

Is this a flaw in the Dictionary casting mechanism, or in my thinking that this should be allowed?

In your thinking. You are expecting that dictionaries should be covariant in their conversions. They are not, for the following reason. Suppose they were, and deduce what could go wrong:

Dictionary<int, List<string>> castMeDict = 
    new Dictionary<int, List<string>>();

Dictionary<int, IEnumerable<string>> getFromDict = 
    (Dictionary<int, IEnumerable<string>>)castMeDict;

castMeDict[123] = new List<string>();
IEnumerable<string> strings = getFromDict[123]; // No problem!
getFromDict[123] = new string[] { "hello" }; // Big problem!

An array of string is convertible to IEnumerable<string> but not to List<string>. You just put something that is not a list of string into a dictionary that can only take list of string.

In C# generic types may be covariant or contravariant if all the following conditions are met:

  • You're using C# 4 or better.
  • The varying generic type is an interface or delegate.
  • The variance is provably typesafe. (The C# specification describes the rules we use to determine variance safety. C# 4.0 Version doc file can be downloaded [here]. See section 23.5.)
  • The type arguments that vary are all reference types.
  • The type has been specifically marked as safe for variance.

Most of those conditions are not met for dictionary -- it is not an interface or delegate, it is not provably safe, and the type is not marked as safe for variance. So, no variance for dictionaries.

IEnumerable<T> by contrast does meet all those conditions. You can convert IEnumerable<string> to IEnumerable<object> in C# 4.

If the subject of variance interests you, consider reading my two dozen articles on the subject:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I see. So the problem is that the castMeDict is still around even though I was only using it to build the dictionary in the first place. – user420667 Dec 19 '11 at 22:10
  • 2
    @user420667: Yep. Now, if there were a way to say "I promise that I will only ever *read* from this dictionary after converting it, and furthermore, that I will only ever *read* from any object that I got by reading the dictionary, and so on" then we could make it safety covariant. But there is no way to express that promise in the CLR type system, and no way to enforce it, aside from *making the dictionary support a read-only interface that is guaranteed to be safe*. That's why you can treat a `List` as an `IEnumerable` -- because there's no way for the `IE` to *write*. – Eric Lippert Dec 19 '11 at 22:21
  • Hm. I guess if I'm only going to be using the object for reading then it shouldn't really be a dictionary but a special dictionary-like class that restricts the setters. A bit like what Jon Hanna has suggested. – user420667 Dec 19 '11 at 22:29
  • 1
    I don't know why I didn't think of suggesting receiving the dictionary through `void ProcessDictionary(Dictionary dict) where T : IEnumerable` sooner. Won't work for all cases, but it will for a lot. – Jon Hanna Dec 20 '11 at 01:07
4

Research contravariance and covariance. For a particular example of why such a case could be a bad thing, check this answer by Jon Skeet.

Community
  • 1
  • 1
drharris
  • 11,194
  • 5
  • 43
  • 56
2

Think about what would happen if it was allowed and you then did:

getFromDict.Add(34, new HashSet<string>);

That's perfectly allowed; HashSet<string> implements IEnumerable<string> and can be added as a value to a Dictionary<int, IEnumerable<string>>. It can't though be added to a Dictionary<int, List<string>>, which is what that object really is.

If you want use it as a read-only IDictionary<int, IEnumerable<string>> then you could get good efficiency from a wrapper class that casts to IEnumerable<string> as it goes.

Otherwise, you need to copy the values into a new Dictionary<int, IEnumerable<string>>.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • actually this is exactly what I needed for efficiency, thanks. – user420667 Dec 19 '11 at 22:02
  • The wrapper idea? Are you clear on how to go about that? – Jon Hanna Dec 19 '11 at 22:03
  • I would imagine it's like you make a class that implements IDictionary> that takes a Dictionary> as a constructor, keeps it around as a member, and overrided the [] and get operators. Is that sort of what you had in mind? Thanks. – user420667 Dec 19 '11 at 22:12
  • 1
    That's exactly it. Implementing `Values` is probably the trickiest bit, though you could just throw `NotImplementedException()` if it's only going to be used internal to an assembly so you know it won't be called. With the members that write you've a choice of either creating a new `List` (testing first if you can just cast to it) or throwing `NotSupportedException` which is acceptable if you return true for `IsReadOnly`. – Jon Hanna Dec 19 '11 at 22:23
  • Apparently the System.Linq.Lookup class is what we were looking for, but I'm not sure if it impacts performance to build it. – user420667 Dec 20 '11 at 00:33
  • Lookup is a class that's a more natural pairing for a lot for the general idea of a key that identifies an enumerable of some type, and may suit your purposes, but it does load in all the items as used. Another idea is if you could "receive" the dictionary through a generic method with the signature `void ProcessDictionary(Dictionary dict) where T : IEnumerable`. You can pass both a `Dictionary>`, a `Dictionary>` etc. into that and work with the commonality of all of them, because it's essentially a different method of each. – Jon Hanna Dec 20 '11 at 01:01