2

I would think it's fairly straightforward to cast an IDictionary<TKey, IList<TValue>> object to an IDictionary<TKey, IEnumerable<TValue>>, but

var val = (IDictionary<TKey, IEnumerable<TValue>>)Value;

throws a System.InvalidCastException, and

var val = Value as IDictionary<TKey, IEnumerable<TValue>>;

makes val null. What is the proper way to cast this?

Jeremy Holovacs
  • 22,480
  • 33
  • 117
  • 254

2 Answers2

8

I would think it's fairly straightforward to cast an IDictionary<TKey, IList<TValue>> object to an IDictionary<TKey, IEnumerable<TValue>>

Absolutely not. It wouldn't be type-safe. Here's an example of why not:

// This is fine...
IDictionary<string, IList<int>> dictionary = new Dictionary<string, IList<int>>();

// Suppose this were valid...
IDictionary<string, IEnumerable<int>> badDictionary = dictionary;

// LinkedList<T> doesn't implement IList<T>
badDictionary["foo"] = new LinkedList<int>();

// What should happen now?
IList<int> bang = dictionary["foo"];

As you can see, that's going to cause problems - we'd be trying to get a LinkedList<int> out when we expect all the values to implement IList<int>. The point of generics is to be type-safe - so which line would you expect to fail? The first, third and fourth lines look pretty clearly valid to me - so the second one is the only one which can fail to compile, and it does...

Now in some cases, it can be done safely. For example, you can convert (in C# 4) from IEnumerable<string> to IEnumerable<object> because IEnumerable<T> only uses T in "output" positions.

See MSDN for more details.

EDIT: Just to clarify - it's easy to create a new dictionary with a copy of the existing key/value pairs, e.g. using link:

var copy = original.ToDictionary<TKey, IEnumerable<TValue>>(pair => pair.Key,
                                                            pair => pair.Value);

You just need to be aware that you now have two separate dictionaries.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Well that doesn't make sense to me. `IEnumerable<>` does not inherit from `IList<>`, it's the other way around, so that seems obviously wrong. You can cast as an ancestor, not a descendant, right? So how does that invalidate downcasting? – Jeremy Holovacs May 01 '12 at 15:12
  • @JeremyHolovacs: My example tried to do *exactly* what you claimed to want to do: convert an `IDictionary>` to a `IDictionary>`. If that's not what you wanted to do, you should clarify your question. – Jon Skeet May 01 '12 at 15:18
  • The last line doesn't seem to do the same at all; there you seem to be trying to convert an `IEnumerable` to an `IList`, and that seems obviously wrong to me. Converting an `IList` to an `IEnumerable` seems like it should be perfectly legit, as `IList` inherits from `IEnumerable`. – Jeremy Holovacs May 01 '12 at 15:22
  • @JeremyHolovacs: No, because the last line is fetching from `dictionary`, not `badDictionary`, and the type of `dictionary` is `IDictionary>`. Read it carefully. The whole point is that the last line (or the third one) would have to fail at *execution time* despite being entirely valid at *compile* time, and that's not good. – Jon Skeet May 01 '12 at 15:25
  • I see, because the dictionary object is referential in nature, adding the value to `badDictionary` adds it to `dictionary`. How does one get around this then? How can I get my `IDictionary>`? – Jeremy Holovacs May 01 '12 at 15:34
  • @JeremyHolovacs: Well, it's not that things are added in two places - it's that there's only one object, and the values of both variables refer to the same object. Like having two pieces of paper with the same house address written on them. You can't do this conversion - it would simply be unsafe to do so. I mean, you could write your own read-only implementation of `IDictionary` which delegated to another, but it would be ugly... If you could tell us what you were trying to achieve (the bigger picture) we might be able to help you more. – Jon Skeet May 01 '12 at 15:38
  • Questions like these have been post at least ten thousand times here! See my suggestion that would help defusing comparable situtions in certain cases: [Querying types in a co- or contravariant way](http://programmers.stackexchange.com/questions/146312/querying-types-in-a-co-or-contravariant-way). – Olivier Jacot-Descombes May 01 '12 at 15:40
  • Well, it seems the answer for this question is "It is not possible". I may ask another question later about how to do what I want, but I may just change what I want to do. – Jeremy Holovacs May 01 '12 at 15:43
  • @JeremyHolovacs: See my edit - if you're happy enough taking a copy, it's simple. But you can't "view" a dictionary of one type as a dictionary of anohter. – Jon Skeet May 01 '12 at 15:52
  • 1
    @JeremyHolovacs: Keep in mind that dictionaries are reference types and that "casting" does not mean "converting" here. After your suggested casting, both variables would point to the same and only dictionary (if the casting was possible). The casting is just saying to the compiler, "Treat A as if it was a B". May be this helps you to understand Jon Skeet's point better. – Olivier Jacot-Descombes May 01 '12 at 15:54
0

This may or may not help you, but I thought I'd throw it out as a supplement to Jon's answer.

If all you need is the dictionary's values, without reference to their keys, you can do this:

IDictionary<TKey, IList<TValue>> dictionary = Whatever();
var values = (IEnumerable<IEnumerable<TValue>>)dictionary.Values;

For this to work, you must be using C# 4.0 or later, and TValue must be constrained to be a reference type. Here's the code, slightly refactored, and with comments to explain:

IDictionary<TKey, IList<TValue>> dictionary = Whatever();

//Values returns an ICollection<IList<TValue>>
ICollection<IList<TValue>> temp1 = dictionary.Values;

//ICollection<T> inherits from IEnumerable<T>
IEnumerable<IList<TValue>> temp2 = temp1;

//IEnumerable<T> is covariant
//There is an implicit reference conversion between IList<T> and IEnumerable<T>
//So there is an implicit reference conversion between IEnumerable<IList<T>>
//and IEnumerable<IEnumerable<T>>
IEnumerable<IEnumerable<TValue>> values = temp2;
phoog
  • 42,068
  • 6
  • 79
  • 117