C# 使用IEnumerable GetGenericArguments获取类型

C# 使用IEnumerable GetGenericArguments获取类型,c#,linq,modelmetadata,C#,Linq,Modelmetadata,我开发了一个用于生成显示和可编辑表的MVC助手(需要一个jquery插件来允许在可编辑表中动态添加和删除带有完整回发的行),例如 与属性一起使用的将包括页脚中的总计、为查看和编辑链接添加列、为复杂类型呈现超链接等 [TableColumn(IncludeTotals = true)] 我将在CodeProject上发布它,但在发布之前,我想解决一个问题。 助手首先从表达式中获取ModelMetadata,检查它是否实现了ICollection,然后获取集合中的类型(请注意,下面的代码片段来自接

我开发了一个用于生成显示和可编辑表的MVC助手(需要一个jquery插件来允许在可编辑表中动态添加和删除带有完整回发的行),例如

与属性一起使用的将包括页脚中的总计、为查看和编辑链接添加列、为复杂类型呈现超链接等

[TableColumn(IncludeTotals = true)]
我将在CodeProject上发布它,但在发布之前,我想解决一个问题。 助手首先从表达式中获取
ModelMetadata
,检查它是否实现了
ICollection
,然后获取集合中的类型(请注意,下面的代码片段来自接受的答案,但如下所述,并不完全正确)

该类型用于为表头(表中可能没有任何行)和表体中的每一行生成
ModelMetadata
(如果某些项是具有附加属性的继承类型,否则会破坏列布局)

我希望能够使用
IEnumerable
而不是
ICollection
,这样就不需要对linq表达式调用
.ToList()

在大多数情况下,
IEnumerable
工作正常,例如

IEnumerable items = MyCollection.Where(i => i....);
是正常的,因为
.GetGenericArguments()
返回一个仅包含一种类型的数组。 问题是,某些查询上的“.GetGenericArguments()”返回2个或更多类型,并且似乎没有逻辑顺序。比如说

IEnumerable items = MyCollection.OrderBy(i => i...);
返回集合中的[0]类型,[1]用于排序的类型

在这种情况下,
.GetGenericArguments()[0]
仍然有效,但是

MyCollection.Select(i => new AnotherItem()
{
  ID = i.ID,
  Name = 1.Name
}
返回[0]原始集合中的类型和[1]另一个项目的类型

因此,
.GetGenericArguments()[1]
是我为
另一个项目
呈现表格所需要的

我的问题是,是否有一种可靠的方法使用条件语句来获取呈现表所需的类型

从我目前的测试来看,使用
.GetGenericArguments().Last()
在所有情况下都有效,除非使用
OrderBy()
,因为排序键是最后一种类型

到目前为止,我尝试过的一些事情包括忽略值类型的类型(通常情况下,
OrderBy()
),但是
OrderBy()
查询可能使用
字符串(可以检查),或者更糟的是,使用重载==、运算符的类(在这种情况下,我无法判断哪种类型是正确的),并且我无法找到一种方法来测试集合是否实现了
iorderenumerable

已解决(使用发布的注释)

私有静态类型GetCollectionType(IEnumerable集合)
{
Type=collection.GetType();
if(type.IsGenericType)
{
Type[]types=Type.GetGenericArguments();
if(types.Length==1)
{
返回类型[0];
}
其他的
{
//如果实现两个IEnumerable,则可以为null
返回类型.GetInterfaces().Where(t=>t.IsGenericType)
.Where(t=>t.GetGenericTypeDefinition()==typeof(IEnumerable))
.SingleOrDefault().GetGenericArguments()[0];
}
}
else if(collection.GetType().IsArray)
{
返回类型:GetElementType();
}
//TODO:谁知道呢,但它可能不适合在表中渲染
返回null;
}

因此,如果我理解的话,您可以提供一些
IEnumerable
,这是(通常)一些任意LINQ查询的结果。您希望可靠地获得
IEnumerable
T
部分?@Chris Yes,尽管它可能只是IEnumerable(例如ArrayList)如果是这样,请尝试迭代实现的接口,找到
IEnumerable
接口并提取
T
部分:
var type=items.GetType().GetInterfaces().Where(T=>T.IsGenericType).Where(T=>T.GetGenericTypeDefinition()==typeof(IEnumerable)).Single().GetGenericArguments()[0]
请注意我使用的
单个
:如果接口没有实现,它将失败,或者如果它实现了两个(或更多)不同的
IEnumerable
接口,然后失败。您可以将其更改为使用
FirstOrDefault
First
,并根据需要处理这些额外的情况。对于您的注释,
ArrayList
未键入。您可以提取第一项并调用其
GetType()
方法。但是如果它没有项,那么你就不走运了:很难说。也不能保证你有一组混合的对象,或者想要使用基类而不是
GetType()
返回的派生类。如果你愿意,如果它没有实现
IEnumerable
(只是
IEnumerable
)然后您可以将其视为单元测试中的类型
对象
?@Chris,检查实现的接口看起来很有希望。关于您使用的
SingleOrDefault
,以及您的注释“如果实现两个IEnumerable,则可能为null”;这不是真的。
SingleOrDefault
将在存在多个异常时引发异常。即使它确实返回了
null
(如果它没有实现任何
IEnumerable
接口,它也会抛出异常),当您尝试调用
GetGenericArguments
时,您将直接获得一个
NullReferenceException
。我建议您分解查询并执行更精确的检查。此外,我个人更喜欢先执行接口检查,而不是默认为第一个泛型类型参数。虽然不太可能,但它是仍然可以有一个类定义,如
class MyStuff:IEnumerable
;在这种情况下,最有可能的是,特别是如果您稍后对它们进行迭代(通过使用
foreach
或调用
GetEnumerator
),您会喜欢
int
类型(因为这将是迭代的内容)而不是
Foo
类型。
IEnumerable items = MyCollection.Where(i => i....);
IEnumerable items = MyCollection.OrderBy(i => i...);
MyCollection.Select(i => new AnotherItem()
{
  ID = i.ID,
  Name = 1.Name
}
private static Type GetCollectionType(IEnumerable collection)
{
  Type type = collection.GetType();
  if (type.IsGenericType)
  {
    Type[] types = type.GetGenericArguments();
    if (types.Length == 1)
    {
      return types[0];
    }
    else
    {
      // Could be null if implements two IEnumerable
      return type.GetInterfaces().Where(t => t.IsGenericType)
        .Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .SingleOrDefault().GetGenericArguments()[0];
    }
  }
  else if (collection.GetType().IsArray)
  {
    return type.GetElementType();
  }
  // TODO: Who knows, but its probably not suitable to render in a table
  return null;
}