Can't convert Dictionary<string, List<string>> to IReadOnlyDictionary<string, IReadOnlyCollection<string>>

1.2k views Asked by At

Can anyone explain this problem?

Dictionary<string, List<string>> x
  = new Dictionary<string, List<string>>();

IReadOnlyDictionary<string, IReadOnlyCollection<string>> y 
  = new Dictionary<string, IReadOnlyCollection<string>>();

y = x;  // CS0266: Cannot implicitly convert type...
3

There are 3 answers

1
Simeon Valev On

This Article perfectly describe the situation which you hit in your code.

Click

Dictionary<TKey, TValue> is able to implement IReadOnlyDictionary<TKey, TValue>, because any code that takes the latter is promising not the modify the dictionary, which is a subset of the functionality the former provides.

But consider the other direction. You start with an IReadOnlyDictionary<TKey, TValue>, and you try to turn it into a regular Dictionary<TKey, TValue>. Now, you've taken something that had previously been promised to not be modified, and turned it into something that may be modified.

Clearly, that just won't do.

Furthermore, you can't just assume that the read-only implementation would throw an exception or something when modified (e.g. run-time safety even though not compile-time safety) because, as you know, you can always get the read-only interface from a mutable implementation.

So to be safe, you have two options:

Just copy the entire dictionary contents into a new object. Wrap the read-only object in an IDictionary<TKey, TValue> implementation that does throw an exception if you try to modify it. Note that the latter does not actually give you what you are specifically asking for. It's close, but it's not an actual instance of Dictionary<TKey, TValue>.

In terms of copying, you have many choices. Personally, I think ToDictionary() is itself just fine. But if you really don't like the verbosity, you can wrap it in an extension method:

public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(
    this IReadOnlyDictionary<TKey, TValue> dictionary)
{
    return dictionary.ToDictionary(x => x.Key, y => y.Value);
}
0
Eric J. On

You can come close to what you're targeting like this:

Dictionary<string, List<string>> x = new Dictionary<string, List<string>>()
  {
    { "Foo", new List<string> {"A", "B", "C"} }
  };

IReadOnlyDictionary<string, ReadOnlyCollection<string>> y = 
    new ReadOnlyDictionary<string, ReadOnlyCollection<string>>(x.ToDictionary(k => k.Key, v => new ReadOnlyCollection<string>(v.Value)));
    
IReadOnlyCollection<string> foo = y["Foo"];

Note that ReadOnlyCollection<T> wraps your original list. It does not copy the elements. Same for the ReadOnlyDictionary<TKey, TValue>.

0
John Wu On

IReadOnlyDictionary is not covariant with respect to its value. This is to provide type safety around the TryGetValue method.

Consider this example:

Dictionary<string, List<string>> x = new Dictionary<string, List<string>>
{
    { "Key", new List<string>() }
};

IReadOnlyDictionary<string, IReadOnlyCollection<string>> y = new Dictionary<string, IReadOnlyCollection<string>>
{
    { "Key", new System.ArraySegment<string>() } //This is allowed because an ArraySegment implements IReadOnlyCollection<string>
};

List<string> foo;
x.TryGetValue("Key", out foo);
foo.Add("Some string");

The last three lines work for x because its value is a List<string>. It would not work if y were assigned to x because its value is not a List<string> and you can't call Add on it. This would not work:

y.TryGetValue("Key", out foo); //Error

Nor would this:

x = y;
x.TryGetValue("Key", out foo); //Error

Because it works for x and not for y, they are not type-compatible, so the cast is not allowed.