11

I have an instance that implements IDictionary<T, K>, I don't know T and K at compiletime, and want to get all elements from it. I don't want to use IEnumerable for some reason, which would be the only non-generic interface implemented by IDictionary.

Code I have so far:

// getting types
Type iDictType = instance.GetType().GetInterface("IDictionary`2");
Type keyType = iDictType.GetGenericArguments()[0];
Type valueType = iDictType.GetGenericArguments()[1];

// getting the keys
IEnumerable keys = (IEnumerable)dictType.GetProperty("Keys")
  .GetValue(instance, null);

foreach (object key in keys)
{
  // ==> this does not work: calling the [] operator
  object value = dictType.GetProperty("Item")
    .GetValue(instance, new object[] {key } );


  // getting the value from another instance with TryGet
  MethodInfo tryGetValue = iDictType.GetMethod("TryGetValue");
  object[] arguments = new object[] { key, null };
  bool hasElement = (bool)tryGetValue.Invoke(otherInstance, arguments);
  object anotherValue = arguments[1];
}

I could also call TryGetValue, but I think it should be possible to call the [] operator. Can anybody help me?

Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
  • I'm not sure that I understand the question. Do you want to use the [] operator instead of getting the value of the Item property via Reflection? – Andy May 12 '09 at 11:19
  • Tried this with a Dictionary and the attempt to use the indexer/ get_Item works for me. – Gishu May 12 '09 at 11:33
  • @Andy: The [] operator actually calls an Item property at runtime, that is not visible at compile time. @Gishu: how did you call the indexer? Is there no property 'Item', only a method 'get_Item'? – Stefan Steinegger May 12 '09 at 14:40

2 Answers2

23

It would be better to figure out the TKey / TValue, and switch into regular code via MakeGenericMethod - like so:

(edit - you could pass in the otherInstance as an argument too, if they are of the same type)

static class Program
{
    static void Main()
    {
        object obj = new Dictionary<int, string> {
            { 123, "abc" }, { 456, "def" } };

        foreach (Type iType in obj.GetType().GetInterfaces())
        {
            if (iType.IsGenericType && iType.GetGenericTypeDefinition()
                == typeof(IDictionary<,>))
            {
                typeof(Program).GetMethod("ShowContents")
                    .MakeGenericMethod(iType.GetGenericArguments())
                    .Invoke(null, new object[] { obj });
                break;
            }
        }
    }
    public static void ShowContents<TKey, TValue>(
        IDictionary<TKey, TValue> data)
    {
        foreach (var pair in data)
        {
            Console.WriteLine(pair.Key + " = " + pair.Value);
        }
    }    
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Ahh, I never saw that hashtable initializer before! +1 – leppie May 12 '09 at 11:42
  • @Leppie - it is part of the collection initializer syntax; you can use any Add method, but if it takes multiple arguments you wrap with an extra set of braces. So {123,"abc"} calls .Add(123,"abc") – Marc Gravell May 12 '09 at 11:47
  • Very nice. There is one problem: iType.GetGenericArguments() could fail, because the concrete type itself does not need to have the same generic arguments as the IDictionary it implements. But I have the code to get the right types already in the question. – Stefan Steinegger May 12 '09 at 14:43
  • I'm not sure it can... note I'm calling that on the *generic interface* type, not the concrete type, and passing in the object *as* that generic interface. Can you show/describe a scenario where that would fail? – Marc Gravell May 12 '09 at 14:54
  • @Marc: You're right, sorry, I certainly didn't read it well. It should also work with GetGenericTypeDefinition and the open generic. Very nice idea with the generic method. I think I can use this on many places to "switch" from runtime types back to generics again. – Stefan Steinegger May 12 '09 at 19:29
  • Does this work on a platform that doesn't support JIT compiling? – Dave Van den Eynde Aug 26 '10 at 18:08
  • @Dave - you'd have to try. The limitations of the light platforms are sometimes hard to predict. – Marc Gravell Aug 27 '10 at 06:07
6

Just for completion, even if Marc Gravell's solution is much nicer, this is the way how it works the way I already started:

object value = dictType.GetMethod("get_Item")
  .Invoke(instance, new object[] { key });

This calls the [] operator of the dictionary.

Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193