In a project, I was getting an object and if it was an IEnumerable<T> I had to display a table with the properties of the object as columns. So I needed to figure out what was the generic type of this enumerable.
Using reflection, you can get the type of any object using:
Type type = o.GetType();
Let’s try this for a few enumerables:
IEnumerable<string> enumerable1 = new List<string> {"test"}; IEnumerable<object> enumerable2 = new List<string> {"test"}; IEnumerable<object> enumerable3 = new List<object> {"test"}; Console.WriteLine(enumerable1.GetType()); Console.WriteLine(enumerable2.GetType()); Console.WriteLine(enumerable3.GetType());
And see what we get:
System.Collections.Generic.List`1[System.String] System.Collections.Generic.List`1[System.String] System.Collections.Generic.List`1[System.Object]
So you get in the brackets the generic type. We just need to find how to get to this info. There ‘s a method called Type.GetGenericArguments(). MSDN says:
Returns an array of Type objects that represent the type arguments of a generic type or the type parameters of a generic type definition.
Since we are handling IEnumerable
IEnumerable<string> enumerable1 = new List<string> {"test"}; IEnumerable<object> enumerable2 = new List<string> {"test"}; IEnumerable<object> enumerable3 = new List<object> {"test"}; Console.WriteLine(enumerable1.GetType().GetGenericArguments().FirstOrDefault()); Console.WriteLine(enumerable2.GetType().GetGenericArguments().FirstOrDefault()); Console.WriteLine(enumerable3.GetType().GetGenericArguments().FirstOrDefault());
And there it is:
System.String System.String System.Object
So that was pretty simple ! Unfortunately in some cases it doesn’t return what I want to get. Let’s assume you have such a class:
class MyEnumerable<T2, T> : List<T> { }
I know this particular class doesn’t make sense but it’s just to keep things simple.
If you run the following code:
IEnumerable<string> enumerable1 = new MyEnumerable<int, string> { "test" }; IEnumerable<object> enumerable2 = new MyEnumerable<int, string> { "test" }; IEnumerable<object> enumerable3 = new MyEnumerable<int, object> { "test" }; Console.WriteLine(enumerable1.GetType().GetGenericArguments().FirstOrDefault()); Console.WriteLine(enumerable2.GetType().GetGenericArguments().FirstOrDefault()); Console.WriteLine(enumerable3.GetType().GetGenericArguments().FirstOrDefault());
You’ll get:
System.Int32 System.Int32 System.Int32
Which is not what I need to get to be able to display my table… What I need is not the first generic argument type for the IEnumerable interface it implements. In order to get to this information, we’ll need to first get the corresponding interface type before using GetGenericArguments. You can get a list of interfaces implemented by a given type like this:
Type[] interfaces = enumerable.GetType().GetInterfaces();
You then get an array of interfaces. Once you have it you need to find the IEnumerable generic interface. In case of our class extending List
- IList`1
- ICollection`1
- IEnumerable`1
- IEnumerable
- IList
- ICollection
- IReadOnlyList`1
- IReadOnlyCollection`1
As you can see IEnumerable is in there twice. This is because both the IEnumerable Interface from System.Collections and the IEnumerable(T) Interface from System.Collections.Generic are implemented. In our case, we are interested in the latter. So let’s fetch it with LINQ. We can identify this with the following criteria:
- IsGenericType == true
- GetGenericTypeDefinition() == typeof (IEnumerable<>)
So the code to get the element type is:
private static Type GetElementTypeOfEnumerable(object o) { var enumerable = o as IEnumerable; // if it's not an enumerable why do you call this method all ? if (enumerable == null) return null; Type[] interfaces = enumerable.GetType().GetInterfaces(); return (from i in interfaces where i.IsGenericType && i.GetGenericTypeDefinition() == typeof (IEnumerable<>) select i.GetGenericArguments()[0]).FirstOrDefault(); }
And the following:
IEnumerable<string> enumerable1 = new MyEnumerable<int, string> { "test" }; IEnumerable<object> enumerable2 = new MyEnumerable<int, string> { "test" }; IEnumerable<object> enumerable3 = new MyEnumerable<int, object> { "test" }; Console.WriteLine(GetElementTypeOfEnumerable(enumerable1)); Console.WriteLine(GetElementTypeOfEnumerable(enumerable2)); Console.WriteLine(GetElementTypeOfEnumerable(enumerable3));
Returns:
System.String System.String System.Object
Now the only remaining problem is that if I get enumerable3 as input, I’ll only see that it’s containing objects and will no be able to show the proper columns because I do not get the information that it’s actually containing a specific type. Of course it’s always better if the collections you get are not lists of objects but list of a specific type but in some cases you don’t have a choice.
One way to workaround this is to peek at the first element in the list and see what’s in there (but only if the specific type cannot be determined by the method above). Of course, if the list contains objects of different types, this will return some wrong information. But in my project I knew that all objects in the enumerable had the same type, so it wasn’t a problem.
So let’s create a second method doing exactly that:
private static Type GetElementTypeOfEnumerable2(object o) { var enumerable = o as IEnumerable; // if it's not an enumerable why do you call this method all ? if (enumerable == null) return null; Type[] interfaces = enumerable.GetType().GetInterfaces(); Type elementType = (from i in interfaces where i.IsGenericType && i.GetGenericTypeDefinition() == typeof (IEnumerable<>) select i.GetGenericArguments()[0]).FirstOrDefault(); //peek at the first element in the list if you couldn't determine the element type if (elementType == null || elementType == typeof (object)) { object firstElement = enumerable.Cast<object>().FirstOrDefault(); if (firstElement != null) elementType = firstElement.GetType(); } return elementType; }
Now executing the following:
IEnumerable<string> enumerable1 = new MyEnumerable<int, string> { "test" }; IEnumerable<object> enumerable2 = new MyEnumerable<int, string> { "test" }; IEnumerable<object> enumerable3 = new MyEnumerable<int, object> { "test" }; Console.WriteLine(GetElementTypeOfEnumerable2(enumerable1)); Console.WriteLine(GetElementTypeOfEnumerable2(enumerable2)); Console.WriteLine(GetElementTypeOfEnumerable2(enumerable3));
Returns:
System.String System.String System.String
Which is exactly what I wanted to see !