如何获取IEnumerable的基本类型

8

可能是重复问题:
从IEnumerable<T>获取类型T

我有一个IEnumerable类型的属性:

public IEnumerable PossibleValues { get; set; }

如何发现它实例化的基本类型?

例如,如果是像这样创建的:

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

我想知道类型是“int”。


5
@Arran,那指的是通用版本。 - Justin Harvey
@Arran:不一定,如果OP展示了一个Nullable<int>数组的方面有任何重要意义,但想要检索int - O. R. Mapper
请注意,有些类型实现了IEnumerable但没有实现IEnumerable<T>,或者它可能实现了IEnumerable<T>但是序列中的所有项实际上都是T的子类型而不是实际的T实例。 - Servy
3个回答

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

这个例子有一些限制,可能会或可能不会涉及到您的应用程序。 它不能处理类型实现 IEnumerable 但不包含 IEnumerable<T> 的情况。如果该类型多次实现 IEnumerable<T>,则它会随意选择一个实现。


谢谢,当我有足够的声望时,我会升级您的答案。 - Raoni Zacaron
我正在使用另一个答案,但学习效果很好。 - Raoni Zacaron
@RaoniZacaron我已经赞同了您的问题,所以现在您有足够的声望来赞同答案(恭喜)。别忘了接受您正在使用的答案! - phoog
@phoog 至少在 .NET 4.5 版本中,如果我尝试调用一个非泛型类型的 GetGenericTypeDefinition() 方法,会抛出 InvalidOperationException 异常;因此已编辑代码,在调用该方法前进行检查... - Tobias J
@TobyJ 谢谢。我可能只是假设它会返回null而不是抛出异常。它不太可能从一个版本到另一个版本发生变化,因为那将是一个破坏性的更改。 - phoog
代码写得漂亮,非常优秀。我会使用 typeof(IEnumerable<>).IsAssignableFrom(type) 来检查 type 是否实现了泛型 IEnumerable 接口。 - Prophet Lamb

3
如果您想要PossibleValues的类型,可以这样做:
var type = PossibleValues.GetType().ToString(); // "System.Nullable`1[System.Int32][]"

如果你想知道PossibleValues中包含的项目类型(假设该数组实际上包含了你问题描述中提到的值),也可以这样做:
var type = PossibleValues.Cast<object>().First().GetType().ToString(); // "System.Int32"



编辑

如果数组可能不包含任何项目,则当然必须进行一些空值检查:

var firstItem = PossibleValues.Cast<object>().FirstOrDefault(o => o != null);
var type = string.Empty;
if (firstItem != null)
{
    type = firstItem.GetType().ToString();
}

1
这就是为什么我说“假设数组实际上有值”的原因。 - bugged87
如果它没有值,那么它可能只是不应该出现类型(这仍然无法工作),但不要抛出异常。 - Servy
@Servy 在这种情况下,空值检查实际上对值类型起作用,因为我正在调用 Cast<object>() - bugged87
@bugged87 我犯了一个错误,它在以该类型的默认值开头的值类型序列上运行得很好。但是,在以null值开头的序列上(即使后面有非null值),它不起作用。您可以添加Where(o => o != null)来解决这个问题。 - Servy
@Servy 已经明白了。我已经在FirstOrDefault()方法中添加了一个条件语句。 - bugged87
显示剩余5条评论

2

有两种现有方法可以查看对象是否实现了 IEnumerable<T>,或者检查集合中第一个项目的类型。第一种方法依赖于对象实际上实现了 IEnumerable<T>,而第二种方法仅在序列中的所有项目都是相同派生类型时才有效。

你可能会问一个有趣的问题,那就是所有项目共同具有哪些类型,或者说最窄的公共类型是什么。

我们将从一个简单的帮助方法开始。给定一个类型,它将返回该类型及其所有基类型的序列。

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

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

接下来我们将有一个方法来获取序列中所有常见类型,首先找到所有最派生的类型,然后获取序列中每个类型的基本类型,最后使用intersect仅获取它们共有的项:

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

注意,在第一种方法中类型的排序是从最派生到最不派生,而Intersect会保持排序,因此结果序列将按照从最派生到最不派生的顺序排列。如果你想找到所有这些类型共同拥有的最窄类型,那么可以简单地在这个方法的结果上使用First。(请注意,由于所有东西都派生自object,因此除非原始的IEnumerable中没有项目,否则总会返回至少一个类型。)

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