4

I recently had to do some reflection in .NET and stumbled upon a strange behavior while comparing generic type definitions.

I had to decide whether a type is an IDictionary<,> by extracting a Type object from a LINQ Expression so I was unable to use the is operator to decide it. Additionally, I had to ignore the type parameters, in other words, the only thing that was important is whether I was dealing with an IDictionary<,> of any key/value type.

I started out with the following:

// Just a placeholder, in my code I have no way of knowing that it
// is a Dictionary of string keys and string values or even that it
// is a dictionary at all.
typeof(Dictionary<string, string>)
    .GetGenericTypeDefinition()
    .GetInterfaces()
    .Any(i => i == typeof(IDictionary<,>))

I assumed that since I was reading the interfaces of a generic type definition I would get generic interface definitions. Even more strange is that when I pasted the above code to LINQPad it returned the following:

typeof(IDictionary<TKey,TValue>) // it appears here
typeof(ICollection<KeyValuePair<TKey,TValue>>) 
typeof(IEnumerable<KeyValuePair<TKey,TValue>>) 
typeof(IEnumerable) 
typeof(IDictionary) 
typeof(ICollection) 
typeof(IReadOnlyDictionary<TKey,TValue>) 
typeof(IReadOnlyCollection<KeyValuePair<TKey,TValue>>) 
typeof(ISerializable) 
typeof(IDeserializationCallback) 

Yet for some reason the comparison in the Any method did not succeed for any of these elements. However, if I get the generic type definitions for the interfaces themselves like so:

typeof(Dictionary<string, string>)
    .GetGenericTypeDefinition()
    .GetInterfaces()
    .Select(i => i.IsGenericType ? i.GetGenericTypeDefinition() : i)
    .Any(i => i == typeof(IDictionary<,>))

then it returns true like it should.

Why is that? Aren't the interfaces returned by the .GetInterfaces() method when called on a generic type definition generic interface definitions themselves already? If so, what's the explanation that when I inspect the returned interfaces in LINQPad they appear to be generic type definitions?

I guess it has something to do with the fact that the interface's type parameters could be completely independent from the implementer's generic arguments, e.g.:

public class MyGenericType<TKey, TValue> : ISomeInterface<object> ...

but still it looks strange that the returned Type objects don't seem to reflect such a case (as I'd expect in this specific example) and from the 'looks' they appear to be generic type definitions yet they aren't identified as that.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Balázs
  • 2,929
  • 2
  • 19
  • 34
  • Instead of `i == typeof(IDictionary<,>)` does `typeof(IDictionary<,>).IsAssignableFrom(i.GetType())` work? – Jeroen van Langen Jan 22 '19 at 15:54
  • @J.vanLangen No, since you cannot expect the method to be able to determine whether something can be assigned to an "indefinite type". But even if it did, it wouldn't explain my question. – Balázs Jan 22 '19 at 15:55
  • 2
    Probably related to https://stackoverflow.com/questions/1735035/generics-open-and-closed-constructed-types, but I can't dive into this right now. – CodeCaster Jan 22 '19 at 16:11

1 Answers1

2

CodeCaster already gave the answer in the comments section. Basically, it is related to open/closed constructs. typeof(IDictionary<,>) is not the same as typeof(IDictionary<string,int>). Moreover it is not the same as typeof(IDictionary<TKey,TValue>).

Basically, typeof(IDictionary<,>) is an unbound type but IDictionary<TKey, TValue>, which you get from your typeof(Dictionary<string, string>).GetGenericTypeDefinition() is an open but constructed type since TKey and TValue are type parameters.


So here is a simple extension method which can do what you want.

public static bool HasGenericInterface(this Type @type,Type genericInterface)
{
    var allInterfaces = @type.GetInterfaces();
    return allInterfaces
        .Where(i => i.IsGenericType)
        .Select(i => i.GetGenericTypeDefinition())
        .Any(i => i.IsAssignableFrom(genericInterface));
 }

Simple usage is as follows

var result = typeof(MyClass).GetType().HasGenericInterface(typeof(IDictionary<,>));
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Hasan Emrah Süngü
  • 3,488
  • 1
  • 15
  • 33
  • Thanks for your response, but I am not looking for code that solves my problem as I already have that and included it in my question as well (by doing the same, calling `GetGenericTypeDefinition()` on the interface types. My question is that the type on which I am calling the `GetInterfaces()` method is already a generic type definition itself, i.e. it's a `Dictionary<,>` and **not** a `Dictionary` or anything like that. I was expecting that if no type parameters are provided then it wouldn't return typed interfaces... – Balázs Jan 23 '19 at 07:54
  • ... and it indeed didn't, LINQPad told me that the interface was already a generic interface type definition yet for some reason, the equality check didn't agree. I was looking for **explaination** for why this is the case. – Balázs Jan 23 '19 at 07:55
  • I have done a little bit of research myself and have found the answer. Please see the updated answer – Hasan Emrah Süngü Jan 24 '19 at 03:33
  • @Balázs, This should give you a better understanding! https://learn.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?redirectedfrom=MSDN&view=netframework-4.7.2#System_Type_IsGenericType – Hasan Emrah Süngü Jan 24 '19 at 08:45