【问题标题】:Get Types using IEnumerable GetGenericArguments使用 IEnumerable GetGenericArguments 获取类型
【发布时间】:2014-07-03 06:32:12
【问题描述】:

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

@Htm.TableDisplayFor(m => m.MyCollection as ICollection)

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

[TableColumn(IncludeTotals = true)]

我即将在 CodeProject 上发布它,但在此之前,我想解决一个问题。 助手首先从表达式中获取ModelMetadata,检查它是否实现了ICollection,然后获取集合中的类型(注意以下sn-p来自SO上接受的答案,但如下所述,并不完全正确)

if (collection.GetType().IsGenericType)
{
  Type type = collection.GetType().GetGenericArguments()[0]

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

foreach (var item in collection)
{
  ModelMetadata itemMetadata = ModelMetadataProviders.Current
    .GetMetadataForType(() => item, type);

我希望能够使用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]AnotherItem的类型

所以.GetGenericArguments()[1] 是我为AnotherItem 呈现表格所需要的。

我的问题是,有没有一种可靠的方法使用条件语句来获取我需要渲染表格的类型?

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

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

【问题讨论】:

  • 所以,如果我理解的话,你提供 some IEnumerable<T> 这是一些 arbitrary LINQ 查询的结果(通常)。并且您想可靠地获得IEnumerable<T>T 部分?
  • @Chris 是的,虽然它可能只是 IEnumerable(例如 ArrayList)
  • 如果是这样,尝试迭代实现的接口,找到IEnumerable<T> 接口并提取T 部分:var type = items.GetType().GetInterfaces().Where(t => t.IsGenericType).Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>)).Single().GetGenericArguments()[0]; 注意我使用Single:如果接口没有实现它失败了,或者如果它实现了两个(或更多)不同的IEnumerable<T> 接口,那么它就会失败。您可以将其更改为使用 FirstOrDefaultFirst 并根据需要处理这些额外情况。
  • 关于您的评论,ArrayList 未输入。您可以提取第一项并调用其GetType() 方法。但是,如果它没有物品,那么您就不走运了:无法分辨。也不保证您有一组混合的对象,或者想要使用基类而不是GetType() 返回的派生类。如果您愿意,如果它没有实现IEnumerable<T>(只是IEnumerable),那么您可以将其视为object类型?
  • @Chris 从目前的单元测试来看,检查实现的接口看起来很有希望。

标签: c# linq modelmetadata


【解决方案1】:

已解决(使用 Chris Sinclair 发布的 cmets)

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

【讨论】:

  • 关于您对SingleOrDefault 的使用以及您对“如果实现两个 IEnumerable 可能为空”的评论;这不是真的。如果有多个,SingleOrDefault抛出异常。即使它确实返回了null(如果它实现任何IEnumerable&lt;T&gt; 接口,它将返回),当你尝试调用GetGenericArguments 时,你会直接得到一个NullReferenceException。我建议您分解查询并执行更准确的检查。
  • 另外,我个人更愿意先进行接口检查,而不是默认使用第一个泛型类型参数。虽然不太可能,但仍有可能拥有像 class MyStuff&lt;Foo&gt; : IEnumerable&lt;int&gt; 这样的类定义;最有可能在这种情况下,特别是如果您稍后对它们迭代(通过使用foreach 或调用GetEnumerator),您会希望使用int 类型(因为这将是迭代的内容) 而不是 Foo 类型。
猜你喜欢
  • 1970-01-01
  • 2011-02-22
  • 2018-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-24
  • 1970-01-01
相关资源
最近更新 更多