Reputation: 1904
example:
public static void DoSomething<K,V>(IDictionary<K,V> items) {
items.Keys.Each(key => {
if (items[key] **is IEnumerable<?>**) { /* do something */ }
else { /* do something else */ }
}
Can this be done without using reflection? How do I say IEnumerable in C#? Should I just use IEnumerable since IEnumerable<> implements IEnumerable?
Upvotes: 35
Views: 14012
Reputation: 1471
Thanks very much for this post. I wanted to provide a version of Konrad Rudolph's solution that has worked better for me. I had minor issues with that version, notably when testing if a Type is a nullable value type:
public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
var interfaceTypes = givenType.GetInterfaces();
foreach (var it in interfaceTypes)
{
if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
return true;
}
if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
return true;
Type baseType = givenType.BaseType;
if (baseType == null) return false;
return IsAssignableToGenericType(baseType, genericType);
}
Upvotes: 147
Reputation: 546153
The previously accepted answer is nice but it is wrong. Thankfully, the error is a small one. Checking for IEnumerable
is not enough if you really want to know about the generic version of the interface; there are a lot of classes that implement only the nongeneric interface. I'll give the answer in a minute. First, though, I'd like to point out that the accepted answer is overly complicated, since the following code would achieve the same under the given circumstances:
if (items[key] is IEnumerable)
This does even more because it works for each item separately (and not on their common subclass, V
).
Now, for the correct solution. This is a bit more complicated because we have to take the generic type IEnumerable`1
(that is, the type IEnumerable<>
with one type parameter) and inject the right generic argument:
static bool IsGenericEnumerable(Type t) {
var genArgs = t.GetGenericArguments();
if (genArgs.Length == 1 &&
typeof(IEnumerable<>).MakeGenericType(genArgs).IsAssignableFrom(t))
return true;
else
return t.BaseType != null && IsGenericEnumerable(t.BaseType);
}
You can test the correctness of this code easily:
var xs = new List<string>();
var ys = new System.Collections.ArrayList();
Console.WriteLine(IsGenericEnumerable(xs.GetType()));
Console.WriteLine(IsGenericEnumerable(ys.GetType()));
yields:
True
False
Don't be overly concerned by the fact that this uses reflection. While it's true that this adds runtime overhead, so does the use of the is
operator.
Of course the above code is awfully constrained and could be expanded into a more generally applicable method, IsAssignableToGenericType
. The following implementation is slightly incorrect1 and I’ll leave it here for historic purposes only. Do not use it. Instead, James has provided an excellent, correct implementation in his answer.
public static bool IsAssignableToGenericType(Type givenType, Type genericType) {
var interfaceTypes = givenType.GetInterfaces();
foreach (var it in interfaceTypes)
if (it.IsGenericType)
if (it.GetGenericTypeDefinition() == genericType) return true;
Type baseType = givenType.BaseType;
if (baseType == null) return false;
return baseType.IsGenericType &&
baseType.GetGenericTypeDefinition() == genericType ||
IsAssignableToGenericType(baseType, genericType);
}
1 It fails when the genericType
is the same as givenType
; for the same reason, it fails for nullable types, i.e.
IsAssignableToGenericType(typeof(List<int>), typeof(List<>)) == false
IsAssignableToGenericType(typeof(int?), typeof(Nullable<>)) == false
I’ve created a gist with a comprehensive suite of test cases.
Upvotes: 43
Reputation: 241950
Thanks for the great info. For convienience, I've refactored this into an extension method and reduced it to a single statement.
public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
{
return givenType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType) ||
givenType.BaseType != null && (givenType.BaseType.IsGenericType && givenType.BaseType.GetGenericTypeDefinition() == genericType ||
givenType.BaseType.IsAssignableToGenericType(genericType));
}
Now it can be easily called with:
sometype.IsAssignableToGenericType(typeof(MyGenericType<>))
Upvotes: 4
Reputation: 42474
I used to think such a situation may be solvable in a way similar to @Thomas Danecker's solution, but adding another template argument:
public static void DoSomething<K, V, U>(IDictionary<K,V> items)
where V : IEnumerable<U> { /* do something */ }
public static void DoSomething<K, V>(IDictionary<K,V> items)
{ /* do something else */ }
But I noticed now that it does't work unless I specify the template arguments of the first method explicitly. This is clearly not customized per each item in the dictionary, but it may be a kind of poor-man's solution.
I would be very thankful if someone could point out anything incorrect I might have done here.
Upvotes: 0
Reputation: 121
A word of warning about generic types and using IsAssignableFrom()...
Say you have the following:
public class MyListBase<T> : IEnumerable<T> where T : ItemBase
{
}
public class MyItem : ItemBase
{
}
public class MyDerivedList : MyListBase<MyItem>
{
}
Calling IsAssignableFrom on the base list type or on the derived list type will return false, yet clearly MyDerivedList
inherits MyListBase<T>
. (A quick note for Jeff, generics absolutely must be wrapped in a code block or tildes to get the <T>
, otherwise it's omitted. Is this intended?) The problem stems from the fact that MyListBase<MyItem>
is treated as an entirely different type than MyListBase<T>
. The following article could explain this a little better. http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html
Instead, try the following recursive function:
public static bool IsDerivedFromGenericType(Type givenType, Type genericType)
{
Type baseType = givenType.BaseType;
if (baseType == null) return false;
if (baseType.IsGenericType)
{
if (baseType.GetGenericTypeDefinition() == genericType) return true;
}
return IsDerivedFromGenericType(baseType, genericType);
}
/EDIT: Konrad's new post which takes the generic recursion into account as well as interfaces is spot on. Very nice work. :)
/EDIT2: If a check is made on whether genericType is an interface, performance benefits could be realized. The check can be an if block around the current interface code, but if you're interested in using .NET 3.5, a friend of mine offers the following:
public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
var interfaces = givenType.GetInterfaces().Where(it => it.IsGenericType).Select(it => it.GetGenericTypeDefinition());
var foundInterface = interfaces.FirstOrDefault(it => it == genericType);
if (foundInterface != null) return true;
Type baseType = givenType.BaseType;
if (baseType == null) return false;
return baseType.IsGenericType ?
baseType.GetGenericTypeDefinition() == genericType :
IsAssignableToGenericType(baseType, genericType);
}
Upvotes: 5
Reputation: 4685
I'd use overloading:
public static void DoSomething<K,V>(IDictionary<K,V> items)
where V : IEnumerable
{
items.Keys.Each(key => { /* do something */ });
}
public static void DoSomething<K,V>(IDictionary<K,V> items)
{
items.Keys.Each(key => { /* do something else */ });
}
Upvotes: 1
Reputation: 35954
I'm not sure I understand what you mean here. Do you want to know if the object is of any generic type or do you want to test if it is a specific generic type? Or do you just want to know if is enumerable?
I don't think the first is possible. The second is definitely possible, just treat it as any other type. For the third, just test it against IEnumerable as you suggested.
Also, you cannot use the 'is' operator on types.
// Not allowed
if (string is Object)
Foo();
// You have to use
if (typeof(object).IsAssignableFrom(typeof(string))
Foo();
See this question about types for more details. Maybe it'll help you.
Upvotes: 0