3

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...
Yahav
  • 211
  • 3
  • 4
  • Please always show the actual errors you are experiencing, it makes it much easier to help. – DavidG Aug 09 '21 at 15:19
  • 3
    [Variance in C#](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/). – Guru Stron Aug 09 '21 at 15:20
  • 1
    @JonSkeet I should know better than to use SO just minutes after I've given blood :) – DavidG Aug 09 '21 at 15:33
  • 1
    @DavidG: It doesn't help that `IReadOnlyCollection` doesn't mean that the collection is read-only, just that the interface is restricted to that. – Jon Skeet Aug 09 '21 at 15:34
  • @JonSkeet Yeah, and even the docs say it *Represents a strongly-typed, read-only collection of elements* – DavidG Aug 09 '21 at 15:36
  • 1
    Does this answer your question? [Cannot convert source type 'List' to IList](https://stackoverflow.com/questions/26046286/) and [convert List> to IList>](https://stackoverflow.com/questions/9005944/) and [Convert IList> to List>](https://stackoverflow.com/questions/38008238/) and [still confused about covariance and contravariance & in/out](https://stackoverflow.com/questions/3445631/) and [C# generic inheritance and covariance part 2](https://stackoverflow.com/questions/14263964/) –  Aug 09 '21 at 15:38
  • @h0r53 Assigning a reference to a variable toward a read only collection is legal. –  Aug 09 '21 at 15:40
  • Related: [`IReadOnlyDictionary` variance](https://github.com/dotnet/csharplang/issues/2175) – John Wu Aug 09 '21 at 22:37

3 Answers3

0

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>.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
0

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.

John Wu
  • 50,556
  • 8
  • 44
  • 80
-1

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);
}
Simeon Valev
  • 27
  • 1
  • 4