在.NET中如何从数组类型获取数组项类型

79

假设有一个System.String[]类型的对象。我可以查询该类型对象以确定它是否为数组

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

但是,我如何从t1获取数组项的类型对象

Type t2 = ....; // should be typeof(System.String)
3个回答

130
您可以使用实例方法Type.GetElementType来实现此目的。
Type t2 = t1.GetElementType();

[Returns] 返回当前数组、指针或引用类型所包含或引用的对象类型,如果当前 Type 不是数组或指针,也不是通过引用传递,或者表示泛型类型或泛型方法中的类型参数,则返回 null。


Chrrrrrrrrrrrrrrrrrrrr Bro!非常感谢你。 - Preet Sangha
8
这也适用于最初提问的数组。为了参考,包含类型的集合可以通过type.GetGenericArguments()[0]访问其类型。 - psaxton
你链接中的注释说:“此方法对于Array类返回null。”因此它可能只适用于T[]形式。但在我使用Array.CreateInstance()生成的Array上测试时似乎没问题... - Mr. Ree

13

感谢 @psaxton 的评论指出 Array 和其他集合之间的区别。作为扩展方法:

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;
    }
}

使用方法:

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

2
我在这里看到一个小问题,那么对于不是集合或数组的泛型类怎么办?我将 if (type.IsArray || type.FullName.StartsWith("System.Collections")) 添加到等式中。 - André
@André 你有例子吗?你是指自定义类吗?因为如果它是枚举类型,那么应该继承自“IEnumerable”;也许我使用“集合”的说法应该改为“可枚举的”? - drzaus
@André 不要比较类型名称的字符串,而是检查 typeof(IEnumerable).IsAssinableFrom(type) 是否成立。 - Shimmy Weitzhandler
@drzaus他的意思是,如果我们检查一个不一定是“IEnumerable”的通用类型,例如IObservable<T>或其他类型,我会将该方法类型更改为GetGenericType - Shimmy Weitzhandler
抛开语义争议,我相信通过获取集合/可枚举类实现的接口列表,并检查类型所提供的IEnumerable<>实现中的泛型类型参数,而不是检查集合类型本身的泛型参数,可以更好地实现预期功能。 当然,这可能会提供多个结果。 但是,您可以使用Linq将集合减少到数组,从而将其减少为一个结果,但这可能比您想要的更昂贵。 - RichardB

2

感谢@drzaus提供的优秀的答案,但它可以被压缩成一行代码(同时检查nullIEnumerable类型):

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

为了避免异常,我添加了null检查器,也许我不应该这样做(可以删除Null Conditional Operators)。 还添加了一个过滤器,使函数仅适用于集合,而不是任何通用类型。

请记住,这也可能会被实现的子类欺骗,从而改变集合的主题,并且实现者决定将集合的通用类型参数移到后面的位置。


C#8和可空性的转换答案:

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

有些变化了,直接复制这段代码会出现以下错误:"运算符 '??' 不能应用于类型为 'Type' 和 'bool' 的操作数"。(.Net Core C#8) - ΩmegaMan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接