SynBiotik
SynBiotik

Reputation: 481

How do I get the type of a generic parameter on IEnumerable<object>?

I created a method to tell me what the Type of the object in an generic IEnumerable is. It seemed like a straightforward affair but I got unexpected results when I tried passing the value collection from a Dictionary to my method. I would like to know how to fix the method to return the right result and ideally I would also like an explanation of why I get the results that I get.

//sample class (target type to get)
public class Person
{
    public string Name { get; set; }    
    public int Age { get; set; }
}

//method that extracts the type
public Type GetItemType(IEnumerable<object> collection)
{
    Type collectionType = collection.GetType();
    Type[] genericArguments = collectionType.GetGenericArguments();
    return genericArguments[0];
}

//sample data for test
var folk = new Dictionary<string, Person>();
folk.Add("Joe", new Person() { Name="Joseph Stalin", Age = 43 });
folk.Add("Tony", new Person() { Name="Winston Churchill", Age = 65 });

IEnumerable<Person> people = folk.Values;
Type itemType = GetItemType(people);

The itemType is "System.String" and not "Person". It seems to take the type generic parameters from the actual Dictionary and not the values collection.

Upvotes: 1

Views: 586

Answers (3)

Kyle
Kyle

Reputation: 6684

The problem here is actually a subtle one. What's going on is that the actual runtime type of folk.Values is a nested class of Dictionary. Specifically it's Dictionary<string, Person>.ValueCollection. Effectively the generic parameters get moved onto the ValueCollection type, and the first one ends up being string.

Ideally, all you should really have to do is change the method signature:

public Type GetItemType<T>( IEnumerable<T> collection )
{
    return typeof( T );
}

To get around this without introducing an actual generic parameter you need to do something like this:

public Type GetItemType(IEnumerable<object> collection)
{
    Type collectionType = collection.GetType();
    collectionType.Dump();

    return collectionType.GetInterfaces()
        .Where( iface => iface.IsGenericType )
        .Where( iface => iface.GetGenericTypeDefinition() == typeof( IEnumerable<> ) )
        .Select( iface => iface.GetGenericArguments()[0] ).FirstOrDefault();
}

In this case we enumerate the interfaces the collection type implements looking for IEnumerable<> and then pull off its generic argument. Beware, this can run into problems if you find a type that implements IEnumerable<> multiple times with different generic argument types.

Upvotes: 1

itsme86
itsme86

Reputation: 19486

The problem you're facing is that there's an underlying type behind IEnumerable<object>, and that type in your case is Dictionary<string, Person>.ValueCollection.

You can see that if you use the debugger and inspect collectionType. To get around this, you could turn the collection into a list by adding .ToList() when initializing people:

IEnumerable<Person> people = folk.Values.ToList();

Now the type behind IEnumerable<object> is List<Person> and should give you the result you're looking for.

An alternative "fix" is to change your method signature to:

public Type GetItemType<T>(IEnumerable<T> collection)

That will return a Person type as well even without turning the Values collection into a list.

Upvotes: 1

Miquel
Miquel

Reputation: 15675

Your GetItemType method gets an IEnumerable<object>. Is it guaranteed that all the items in the IEnumerable will be of the same type?

If not, you need to return an IEnumerable of types. If yes, you need only look at the very first one, and return collection.First().GetType()

Is this what you were looking for?

Oh, actually, check out @itsme86 comment, that's a cleaner way to do what you want to do

Upvotes: 0

Related Questions