IDictionary<TKey, TValue> in .NET 4 not covariant

.NetDictionary.Net 4.0Covariance

.Net Problem Overview


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.

.Net Solutions


Solution 1 - .Net

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

Solution 2 - .Net

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.

Solution 3 - .Net

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; }
}
}

Solution 4 - .Net

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

Solution 5 - .Net

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;
    }   
}

Solution 6 - .Net

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionherzmeisterView Question on Stackoverflow
Solution 1 - .NetmmxView Answer on Stackoverflow
Solution 2 - .NetjasonView Answer on Stackoverflow
Solution 3 - .NetwavydavyView Answer on Stackoverflow
Solution 4 - .NetPaul CreaseyView Answer on Stackoverflow
Solution 5 - .NetMaslowView Answer on Stackoverflow
Solution 6 - .NetNick MorhunView Answer on Stackoverflow