-1

Why doesn't this work in C#?

var dict1 = new Dictionary<int, System.Collections.IEnumerable>();
dict1[0] = new List<string>(); // OK, because implements IEnumerable

var dict2 = new Dictionary<int, List<string>>();
dict1 = (Dictionary<int, System.Collections.IEnumerable>)dict2; // Compiler error CS0030

We can clearly see that every value in dict2 must implement IEnumerable because it is a List<T>, so why can't I assign it to a Dictionary<int, System.Collections.IEnumerable>? Is there a way to do this?

My use case here is that I have some code that wants to use the more specific stuff in (in this example) the List<string>, but some other code that needs to take an IEnumerable dictionary. I can explicitly cast in every predicate to get access to the more specific stuff, but it seems pretty messy and I'd rather have a reference to the (same) Dictionary where its value's type is actually List<string>:

var dict1 = new Dictionary<int, System.Collections.IEnumerable>();
dict1[0] = new List<string>(); // OK, because implements IEnumerable
var result = dict1.Where(x => ((List<string>)x.Value).Capacity > 100);
Jez
  • 27,951
  • 32
  • 136
  • 233
  • IEnumerable is so archaic, though, why even use such weak types? It's like Java generics, awful. – doug65536 Oct 26 '22 at 12:16
  • It's just for example purposes. The specific types don't matter, the principle is what I'm asking about, the fact that `IEnumerable` is required, and `List` implements it. – Jez Oct 26 '22 at 12:17
  • If this cast was permitted, clients could use `dict1` to insert values into `dict2` with a type other than `List`, which would be Bad. Going through `IReadOnlyDictionary` won't help either, because generics are invariant. You could explicitly write a wrapper type that achieves it, but this is probably more trouble than it's worth. If your dictionary is not too big a call to `.ToDictionary()` to produce a properly-typed copy would do it (`dict2.ToDictionary(kvp => kvp.Key, kvp => (System.Collections.IEnumerable) kvp.Value)`), obviously at the cost of a copy. – Jeroen Mostert Oct 26 '22 at 12:19
  • The following compiles. Don't knowif it will work : var dict1 = dict2.GroupBy(x => x.Key).ToDictionary(x => x.Key, y => y.ToList()); – jdweng Oct 26 '22 at 12:23
  • @JeroenMostert OK so given that I need to access (say) the extra stuff implemented by `List` in some places, but I have a `Dictionary`, do you think my explicit casting in the predicate lambdas is the best solution? – Jez Oct 26 '22 at 12:24

2 Answers2

1

Take your code a step further and you'll see why:

var dict1 = new Dictionary<int, System.Collections.IEnumerable>();
dict1[0] = new List<string>(); // OK, because implements IEnumerable

var dict2 = new Dictionary<int, List<string>>();
dict1 = (Dictionary<int, System.Collections.IEnumerable>)dict2;

dict1.Add(1, new ArrayList());

ArrayList implements IEnumerable so, based on the type of dict1, that should work. It won't though, because the underlying object will only accept List<string> objects. Such situations would be all too common if what you describe were to be allowed.

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
  • OK so given that I need to access (say) the extra stuff implemented by `List` in some places, but I have a `Dictionary`, do you think my explicit casting in the predicate lambdas is the best solution? – Jez Oct 26 '22 at 12:24
0

I was able to achieve this using a generic type constraint. My containing class now says:

public class MyClass<TId, TValue>
    where TId : struct
    where TValue : System.Collections.IEnumerable {

    [...]

    public Dictionary<TId, TValue> Field;

    [...]

}

This allows me to set the Field Dictionary to be of the type that I want as long as it implements IEnumerable, so when I want to use that specific type's abilities, I can, but when the containing class wants to use IEnumerable from that Dictionary, it can too:

var test = new MyClass();
test.Field = new Dictionary<int, List<string>>();
Jez
  • 27,951
  • 32
  • 136
  • 233