8

Possible Duplicate:
getting type T from IEnumerable<T>

I Have a property type of IEnumerable

public IEnumerable PossibleValues { get; set; }

How can I discover the base type that it was instanced?

For example, if it was created like this:

PossibleValues = new int?[] { 1, 2 }

I want to know that type is 'int'.

Community
  • 1
  • 1
Raoni Zacaron
  • 367
  • 5
  • 15
  • 5
    @Arran, that refers to the generic version – Justin Harvey Oct 04 '12 at 13:32
  • @Arran: Not necessarily, if there is any significance in the aspect that the OP shows an array of `Nullable`, but wants to retrieve `int`. – O. R. Mapper Oct 04 '12 at 13:32
  • Note that there are types that implement `IEnumerable` but not `IEnumerable`, or it could implement `IEnumerable` but have all of the items in the sequence actually be sub-types of T rather than actual T instances. – Servy Oct 04 '12 at 13:43

3 Answers3

10
Type GetBaseTypeOfEnumerable(IEnumerable enumerable)
{
    if (enumerable == null)
    {
        //you'll have to decide what to do in this case
    }

    var genericEnumerableInterface = enumerable
        .GetType()
        .GetInterfaces()
        .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));

    if (genericEnumerableInterface == null)
    {
        //If we're in this block, the type implements IEnumerable, but not IEnumerable<T>;
        //you'll have to decide what to do here.

        //Justin Harvey's (now deleted) answer suggested enumerating the 
        //enumerable and examining the type of its elements; this 
        //is a good idea, but keep in mind that you might have a
        //mixed collection.
    }

    var elementType = genericEnumerableInterface.GetGenericArguments()[0];
    return elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(Nullable<>)
        ? elementType.GetGenericArguments()[0]
        : elementType;
}

This example has some limitations, which may or may not concern you in your application. It doesn't handle the case where the type implements IEnumerable but not IEnumerable<T>. If the type implements IEnumerable<T> more than once, it picks one implementation arbitrarily.

Tobias J
  • 19,813
  • 8
  • 81
  • 66
phoog
  • 42,068
  • 6
  • 79
  • 117
  • Thanks, When I have reputation enough I up your answer. – Raoni Zacaron Oct 04 '12 at 13:56
  • I am using another answer, but the learning was good. – Raoni Zacaron Oct 04 '12 at 14:02
  • @RaoniZacaron I have upvoted your question, so you now have enough reputation to upvote answers (congratulations). Don't forget to accept the answer that you are using! – phoog Oct 04 '12 at 14:03
  • @phoog at least with .NET 4.5, I'm getting an InvalidOperationException if I try to call `GetGenericTypeDefinition()` on a type that isn't generic; edited to check for that before calling... – Tobias J Nov 05 '15 at 00:12
  • @TobyJ thanks. I probably just assumed it would return null rather than throwing. It's unlikely to have changed from one version to the next as that would be a breaking change. – phoog Nov 05 '15 at 00:20
  • Beautiful code and well written. I would use `typeof(IEnumerable<>).IsAssignableFrom(type)` for checking if the `type` implements the generic IEnumerable interface. – Prophet Lamb Apr 27 '20 at 15:53
3

You can do this if you want the type of PossibleValues:

var type = PossibleValues.GetType().ToString(); // "System.Nullable`1[System.Int32][]"

Or you can do this if you want the type of an item contained in PossibleValues (assuming the array actually has values as described in your question):

var type = PossibleValues.Cast<object>().First().GetType().ToString(); // "System.Int32"



EDIT

If it's a possibility that the array may contain no items, then you'll have to do some null checking, of course:

var firstItem = PossibleValues.Cast<object>().FirstOrDefault(o => o != null);
var type = string.Empty;
if (firstItem != null)
{
    type = firstItem.GetType().ToString();
}
bugged87
  • 3,026
  • 2
  • 26
  • 42
  • You'll want to check that the sequence has values before pulling the first one. – Servy Oct 04 '12 at 13:47
  • 1
    That's why I stated "assuming the array actually has values". – bugged87 Oct 04 '12 at 13:51
  • If it doesn't have values it should probably just not come up with a type (which is still not working) but not throw an exception. – Servy Oct 04 '12 at 13:53
  • Thanks, When I have reputation enough I up your answer. I am using your second suggestion, fits right in what I needed. – Raoni Zacaron Oct 04 '12 at 13:55
  • @RaoniZacaron In the mean time, I think you can still accept answers regardless of reputation level (click the checkbox under the voting buttons). – bugged87 Oct 04 '12 at 14:00
  • @bugged87 Your check if there are any items won't work on value types, or if the enumeration simply starts with a default value. – Servy Oct 04 '12 at 14:00
  • @Servy I added some more info to my answer based on your suggestion. – bugged87 Oct 04 '12 at 14:01
  • @Servy The null check does actually work for value types in this case since I'm calling `Cast()` – bugged87 Oct 04 '12 at 14:13
  • @bugged87 My mistake, it does work just fine on a sequence of value types that starts with that type's default value. It just doesn't work on a sequence that starts with a null value (even if there are non-null values later on). You could add a `Where(o => o != null)` to take care of that. – Servy Oct 04 '12 at 14:22
  • @Servy Point taken. I've added a conditional statement to the FirstOrDefault() method. – bugged87 Oct 04 '12 at 17:34
2

Two existing approaches are to see if the object implements IEnumerable<T> or to check the type of the first item in the set. The first relies on the object actually implementing IEnumerable<T>, and the second only works if all of the items in the sequence are of the same derived type.

One interesting question you might ask is what types do all of the items have in common, or what is the most narrow type that is common among all of the items.

We'll start out with a simple helper method. Given a type it will return a sequence of that type and all of it's base types.

public static IEnumerable<Type> getBaseTypes(Type type)
{
    yield return type;

    Type baseType = type.BaseType;
    while (baseType != null)
    {
        yield return baseType;
        baseType = baseType.BaseType;
    }
}

Next we'll have a method to get all of the common types for a sequence by first finding all of the most derived types, then getting all of the base types for each Type in the sequence, and finally using intersect to get only those items they all have in common:

public static IEnumerable<Type> getCommonTypes(IEnumerable source)
{
    HashSet<Type> types = new HashSet<Type>();
    foreach (object item in source)
    {
        types.Add(item.GetType());
    }

    return types.Select(t => getBaseTypes(t))
        .Aggregate((a, b) => a.Intersect(b));
}

Note that the ordering of the types in the first method is from most derived to least derived, and Intersect maintains ordering, so the resulting sequence will be in order from most derived to least derived. If you want to find the most narrow type common to all of these types then you can simply use First on the result of this method. (Note that since everything derives from object there will always be at least one type returned here unless there are no items in the original IEnumerable.

Servy
  • 202,030
  • 26
  • 332
  • 449