79

Say I have an System.String[] type object. I can query the type object to determine if it is an array

Type t1 = typeof(System.String[]);
bool isAnArray = t1.IsArray; // should be true

However how do I get a type object of the array item from t1

Type t2 = ....; // should be typeof(System.String)
Preet Sangha
  • 64,563
  • 18
  • 145
  • 216

3 Answers3

129

You can use the instance method Type.GetElementType for this purpose.

Type t2 = t1.GetElementType();

[Returns] the type of the object encompassed or referred to by the current array, pointer, or reference type, or null if the current Type is not an array or a pointer, or is not passed by reference, or represents a generic type or a type parameter in the definition of a generic type or generic method.

Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
Ani
  • 111,048
  • 26
  • 262
  • 307
  • Chrrrrrrrrrrrrrrrrrrrr Bro!. Thanks a million. – Preet Sangha Nov 09 '10 at 02:16
  • 8
    This works for Arrays as was originally questioned. For reference, collections contained type can be accessed as type.GetGenericArguments()[0] – psaxton Aug 23 '13 at 20:40
  • The Remarks in your link said "This method returns `null` for the `Array` class." So it may only work on `T[]` forms. But it seems fine in my test on an `Array` generated by `Array.CreateInstance()`... – Mr. Ree Jul 11 '15 at 08:58
13

Thanks to @psaxton comment pointing out the difference between Array and other collections. As an extension method:

public static class TypeHelperExtensions
{
    /// <summary>
    /// If the given <paramref name="type"/> is an array or some other collection
    /// comprised of 0 or more instances of a "subtype", get that type
    /// </summary>
    /// <param name="type">the source type</param>
    /// <returns></returns>
    public static Type GetEnumeratedType(this Type type)
    {
        // provided by Array
        var elType = type.GetElementType();
        if (null != elType) return elType;

        // otherwise provided by collection
        var elTypes = type.GetGenericArguments();
        if (elTypes.Length > 0) return elTypes[0];

        // otherwise is not an 'enumerated' type
        return null;
    }
}

Usage:

typeof(Foo).GetEnumeratedType(); // null
typeof(Foo[]).GetEnumeratedType(); // Foo
typeof(List<Foo>).GetEnumeratedType(); // Foo
typeof(ICollection<Foo>).GetEnumeratedType(); // Foo
typeof(IEnumerable<Foo>).GetEnumeratedType(); // Foo

// some other oddities
typeof(HashSet<Foo>).GetEnumeratedType(); // Foo
typeof(Queue<Foo>).GetEnumeratedType(); // Foo
typeof(Stack<Foo>).GetEnumeratedType(); // Foo
typeof(Dictionary<int, Foo>).GetEnumeratedType(); // int
typeof(Dictionary<Foo, int>).GetEnumeratedType(); // Foo, seems to work against key
Community
  • 1
  • 1
drzaus
  • 24,171
  • 16
  • 142
  • 201
  • 2
    I do see a small issue here, what about classes with Generics that aren't Colleciton or array. I added if (type.IsArray || type.FullName.StartsWith("System.Collections")) to the equation. – André Jul 04 '14 at 09:13
  • @André what's an example? do you mean a custom class? because really, if it's an enumerated type it should (?) inherit from `IEnumerable`; maybe my use of 'collections' should have been 'enumerable' then? – drzaus Aug 11 '14 at 14:59
  • @André don't compare type names' strings, instead check if `typeof(IEnumerable).IsAssinableFrom(type)`. – Shimmy Weitzhandler May 07 '17 at 00:24
  • @drzaus he meant what if we check a generic type that does is not necessarily an `IEnumerable`, for example `IObservable`, or whatever. I'd change the method type to `GetGenericType`. – Shimmy Weitzhandler May 07 '17 at 00:27
  • Semantic debates aside, I believe that the intended functionality would be better accomplished by getting the list of interfaces implemented by the collection/enumerable, and checking the generic type parameters in the IEnumerable<> implementations that the type provides, in lieu of checking generic parameters on the collection type itself. This, of course, could end up providing more than one result. You could reduce it to one result however, by using Linq to reduce the collection to an array, but this might be more expensive than you want. – RichardB Sep 16 '22 at 16:39
2

Thanks to @drzaus for his nice answer, but it can be compressed to a oneliner (plus check for nulls and IEnumerable type):

public static Type GetEnumeratedType(this Type type) =>
   type?.GetElementType()
   ?? typeof(IEnumerable).IsAssignableFrom(type)
   ? type.GenericTypeArguments.FirstOrDefault()
   : null;

Added null checkers to avoid exception, maybe I shouldn't (feel free to remove the Null Conditional Operators). Also added a filter so the function only works on collections, not any generic types.

And bear in mind that this could also be fooled by implemented subclasses that change the subject of the collection and the implementor decided to move the collection's generic-type-argument to a later position.


Converted answer for C#8 and nullability:

public static Type GetEnumeratedType(this Type type) => 
        ((type?.GetElementType() ?? (typeof(IEnumerable).IsAssignableFrom(type)
            ? type.GenericTypeArguments.FirstOrDefault()
            : null))!;
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • Something has changed, copying this code verbatim I get this error: "Operator '??' cannot be applied to operands of type 'Type' and 'bool'". (.Net Core C#8) – ΩmegaMan Nov 11 '20 at 16:14