63

The IDictionary<TKey, TValue> in .NET 4 / Silverlight 4 does not support covariance, i.e. I can't do a

IDictionary<string, object> myDict = new Dictionary<string, string>();

analog to what I can do with IEnumerable<T>s now.

Probably boils down to the KeyValuePair<TKey, TValue> not being covariant either. I feel that covariance should be allowed in dictionaries at least for the values.

So is that a bug or a feature? Will it ever come, maybe in .NET 37.4?

UPDATE (2 years later):

There will be an IReadOnlyDictionary<TKey, TValue> in .NET 4.5, but it won't be covariant either :·/, because it derives from IEnumerable<KeyValuePair<TKey, TValue>>, and KeyValuePair<TKey, TValue> is not an interface and thus cannot be covariant.

The BCL team would have to redesign a lot to come up and use some ICovariantPair<TKey, TValue> instead. Also strongly-typed indexers á la this[TKey key] aren't possible for covariant interfaces. A similar end can only be achieved by placing an extension method GetValue<>(this IReadOnlyDictionary<TKey, TValue> self, TKey key) somewhere which would somehow internally have to call an an actual implementation, which arguably looks like a quite messy approach.

herzmeister
  • 11,101
  • 2
  • 41
  • 51
  • 8
    Thanks for providing the update on .NET 4.5. IMHO it would be useful to have covariance on a read-only dictionary so it's too bad that it's not looking like it will be supported. – dcstraw Mar 21 '12 at 20:12
  • A `IReadOnlyDictionary` wrapper that supports upcasting can be found [here](https://stackoverflow.com/a/56595603/11178549). It can wrap a dictionary of strings and expose it as a dictionary of objects. – Theodor Zoulias Jun 14 '19 at 14:17

6 Answers6

57

It's a feature. .NET 4.0 only supports safe covariance. The cast you mentioned is potentially dangerous as you could add a non-string element to the dictionary if that was possible:

IDictionary<string, object> myDict = new Dictionary<string, string>();
myDict["hello"] = 5; // not an string

On the other hand, IEnumerable<T> is a read-only interface. The T type parameter is only in its output positions (return type of the Current property) so it's safe to treat IEnumerable<string> as an IEnumerable<object>.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • Ahh ok, of course, I indeed was intending for read-only use. The .NET library surely does miss a read-only Dictionary type. Someone should post another question about that issue one of these days. ;-) – herzmeister Jan 27 '10 at 19:11
  • 2
    In theory covariance is safe, but a quirk from .Net 1.0 may throw a slight spanner in the works. Because `Derived[]` is considered to inherit from `Base[]`, a `Derived[]` will implement `IList`; such an `IList` will work correctly for reading, but will throw an exception when written to. – supercat Dec 03 '12 at 18:01
  • Stupid because `ReadOnlyDictionary` and `ImmutableDictionary` should not have this issue – wilmol Mar 02 '23 at 02:31
14

But then you could say

myDict.Add("Hello, world!", new DateTime(2010, 1, 27));

which would fail miserably. The issue is that the TValue in IDictionary<TKey, TValue> is used in both input and output positions. To wit:

myDict.Add(key, value);   

and

TValue value = myDict[key];

So is that a bug or a feature?

It's by design.

Will it ever come, maybe in .NET 37.4?

No, it's inherently unsafe.

jason
  • 236,483
  • 35
  • 423
  • 525
5

I had a similar problem, but with more specialised derived types (rather than object which everything derives from)

The trick is to make the method generic and put a where clause putting the relevant restriction. Assuming that you're dealing with base types and derived types, the following works:

using System;
using System.Collections.Generic;

namespace GenericsTest
{
class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();

        p.Run();
    }

    private void Run()
    {

        Dictionary<long, SpecialType1> a = new Dictionary<long, SpecialType1> {
        { 1, new SpecialType1 { BaseData = "hello", Special1 = 1 } },
        { 2, new SpecialType1 { BaseData = "goodbye", Special1 = 2 } } };

        Test(a);
    }

    void Test<Y>(Dictionary<long, Y> data) where Y : BaseType
    {
        foreach (BaseType x in data.Values)
        {
            Console.Out.WriteLine(x.BaseData);
        }
    }
}

public class BaseType
{
    public string BaseData { get; set; }
}

public class SpecialType1 : BaseType
{
    public int Special1 { get; set; }
}
}
wavydavy
  • 369
  • 3
  • 8
  • this is a great workaround! It allows me to do exactly what I need to do for code re-use, and completely avoids the problem that covariance would introduce. – jltrem Nov 11 '14 at 20:11
2

.NET 4 only supports out covariance not in. It works with IEnumerable because IEnumerable is read only.

Paul Creasey
  • 28,321
  • 10
  • 54
  • 90
  • 12
    "in covariance" is a misnomer. That would be contravariance, and it is supported in .NET 4 and is useful in certain scenarios. – dcstraw Mar 21 '12 at 20:08
0

Assuming you only need a specific operations from a Dictionary, you could create a wrapper:

class Container
{
    private IDictionary<string, string> myDict = new Dictionary<string, string>();

    public object GetByKey(string key) => myDict[key];
}

or even implement the interface that you need in Container.

Nick Morhun
  • 175
  • 1
  • 6
-1

A work around for a specific type of useful covariance on IDictionary

public static class DictionaryExtensions
{
    public static IReadOnlyDictionary<TKey, IEnumerable<TValue>> ToReadOnlyDictionary<TKey, TValue>(
        this IDictionary<TKey, List<TValue>> toWrap)
    {
        var intermediate = toWrap.ToDictionary(a => a.Key, a => a.Value!=null ? 
                                        a.Value.ToArray().AsEnumerable() : null);
        var wrapper = new ReadOnlyDictionary<TKey, IEnumerable<TValue>>(intermediate);
        return wrapper;
    }   
}
Ryan
  • 7,835
  • 2
  • 29
  • 36
Maslow
  • 18,464
  • 20
  • 106
  • 193