2

Problem

using System.Collections.Generic;
using System.Linq;

Given a base class and 2 derived classes:

class Base{}
class Derived0 : Base{}
class Derived1 : Base{}

I have two dictionaries, of types:

IDictionary<String, Derived0> d0 = ...;
IDictionary<String, Derived1> d1 = ...;

I want to find the union of the two dictionaries, which should have type IDictionary<String, Base>. (I already know that the keys are unique across both dictionaries, so I don't care about behavior when there are duplicates.)


Attempts

If they were of the same type I could use

var union = d0.Concat(d1);

But this gives the error (compiling using dmcs):

Test.cs(15,20): error CS0411: The type arguments for method `System.Linq.Queryable.Concat<TSource>(this System.Linq.IQueryable<TSource>, System.Collections.Generic.IEnumerable<TSource>)' cannot be inferred from the usage. Try specifying the type arguments explicitly

If I explicitly give Base as a type argument:

IDictionary<string, Base> union = d0.Concat<Base>(d1);

it still doesn't work:

Test.cs(15,42): error CS1928: Type `System.Collections.Generic.IDictionary<string,Derived0>' does not contain a member `Concat' and the best extension method overload `System.Linq.Enumerable.Concat<Base>(this System.Collections.Generic.IEnumerable<Base>, System.Collections.Generic.IEnumerable<Base>)' has some invalid arguments
/usr/lib/mono/gac/System.Core/4.0.0.0__b77a5c561934e089/System.Core.dll (Location of the symbol related to previous error)
Test.cs(15,42): error CS1929: Extension method instance type `System.Collections.Generic.IDictionary<string,Derived0>' cannot be converted to `System.Collections.Generic.IEnumerable<Base>'

In principle, variance shouldn't matter here since I'm creating a new dictionary object, but I can't figure out how to represent it in the type system.

Community
  • 1
  • 1
Mechanical snail
  • 29,755
  • 14
  • 88
  • 113

2 Answers2

4

IDictionary<TKey, TValue> and KeyValuePair<TKey, TValue> are not variant... So:

var sequence0 = d0.Select(kvp => new KeyValuePair<String, Base>(kvp.Key, kvp.Value));
var sequence1 = d1.Select(kvp => new KeyValuePair<String, Base>(kvp.Key, kvp.Value));
var dictionaryOfBase = sequence0.Concat(sequence1).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Dennis
  • 37,026
  • 10
  • 82
  • 150
  • Today, are you aware of a more concise syntax to realize this ? – Spotted Mar 25 '19 at 12:13
  • 1
    @Spotted: since nothing changed in public surface for `KeyValuePair`, you can't do anything else here. Maybe, turn this code into extension method. – Dennis Mar 25 '19 at 12:53
0

You can use these extension methods:

    class Base { }
    class Derived0 : Base { }
    class Derived1 : Base { }

    class Program {
        static void Main(string[] args) {
            var d0 = new Dictionary<string, Derived0>();
            var d1 = new Dictionary<string, Derived1>();
            var b = d0.Merge<string, Derived0, Derived1, Base>(d1);
        }
    }

    public static class DictionaryExtensions {
        public static Dictionary<TKey, TBase> Merge<TKey, TValue1, TValue2, TBase>(this IDictionary<TKey, TValue1> thisDictionary, IDictionary<TKey, TValue2> thatDictionary)
            where TValue1 : TBase
            where TValue2 : TBase {
            var resultDictionary = new Dictionary<TKey, TBase>();
            resultDictionary.AddRange(thisDictionary);
            resultDictionary.AddRange(thatDictionary);

            return resultDictionary;
        }

        public static void AddRange<TKey, TBase, TValue>(this IDictionary<TKey, TBase> dictionary, IDictionary<TKey, TValue> dictionaryToAdd) where TValue : TBase {
            foreach (var kvp in dictionaryToAdd) {
                dictionary.Add(kvp.Key, kvp.Value);
            }
        }
    }
Maarten
  • 22,527
  • 3
  • 47
  • 68