从 IEnumerable 转换为 IEnumerable<object>

Posted

技术标签:

【中文标题】从 IEnumerable 转换为 IEnumerable<object>【英文标题】:Cast from IEnumerable to IEnumerable<object> 【发布时间】:2016-07-19 07:21:58 【问题描述】:

我更喜欢使用IEnumerable&lt;object&gt;,因为LINQ扩展方法是在上面定义的,而不是IEnumerable,所以我可以使用,例如range.Skip(2)。但是,我也更喜欢使用IEnumerable,因为无论T 是引用类型还是值类型,T[] 都可以隐式转换为IEnumerable。对于后一种情况,不涉及拳击,这很好。结果,我可以做到IEnumerable range = new[] 1, 2, 3 。似乎不可能将两全其美的东西结合起来。无论如何,我选择在IEnumerable 安顿下来,并在需要应用 LINQ 方法时进行某种类型的转换。

从this SO 线程,我知道range.Cast&lt;object&gt;() 能够胜任这项工作。但它会产生性能开销,我认为这是不必要的。我尝试执行像(IEnumerable&lt;object&gt;)range 这样的直接编译时转换。根据我的测试,它适用于引用元素类型,但不适用于值类型。有什么想法吗?

仅供参考,问题源于this GitHub 问题。而我使用的测试代码如下:

static void Main(string[] args)

    // IEnumerable range = new[]  1, 2, 3 ; // won't work
    IEnumerable range = new[]  "a", "b", "c" ;
    var range2 = (IEnumerable<object>)range;
    foreach (var item in range2)
    
        Console.WriteLine(item);
    

【问题讨论】:

你的意思是IEnumerable&lt;object&gt;,还是IEnumerable&lt;T&gt;?如果是前者,你为什么用&lt;object&gt;而不是&lt;T&gt; @Maarten 是的,我的意思是使用IEnumerable&lt;object&gt;,因为我需要处理一般情况,并支持不同的元素类型。 你为什么要和IEnumerable一起工作呢?为什么不改为IEnumerable&lt;int&gt; range = new[] 1, 2, 3 ;? - 顺便提一句。 IEnumerable 确实框,对吗?如果您在(非通用)IEnumerable 上使用foreach,您将获得object。另一方面,一个 generic 可枚举 (IEnumerable&lt;T&gt;) 没有 not 框。 @Corak 正如对 Maarten 评论的回复所说,我的意思是使用IEnumerable&lt;object&gt;,因为我需要处理一般情况,并支持不同的元素类型。 关于IEnumerable,我认为拳击在每次枚举时都会发生,但不是在分配时发生。查看更新问题中的测试代码。 仍然不确定我是否理解。你说你想处理“一般情况”。那么“一般情况”将是IEnumerable&lt;T&gt;。因此,如果您现在有一个类似doStuff(IEnumerable items)/* ... */ 的方法,您可以将其更改为doStuff&lt;T&gt;(IEnumerable&lt;T&gt; items)/* ... */,然后您就有一个 实现,您可以在其中处理int[]List&lt;string&gt;IEnumerable&lt;YourClass&gt;,但在一个类型安全(和非装箱)方式。 【参考方案1】:

根据我的测试,它适用于参考元素类型,但不适用于 值类型。

正确。这是因为IEnumerable&lt;out T&gt; 是协变的,而co-variance/contra-variance is not supported for value types。

我知道 range.Cast() 能够完成这项工作。但它 会产生性能开销,我认为这是不必要的。

IMO 如果您想要一组具有给定值类型的对象集合,则性能成本(由装箱带来)是不可避免的。使用非泛型IEnumerable 不会避免装箱,因为IEnumerable.GetEnumerator 提供了IEnumerator,其.Current 属性返回object。我宁愿总是使用IEnumerable&lt;T&gt; 而不是IEnumerable。所以只需使用.Cast 方法,忘记装箱。

【讨论】:

【参考方案2】:

反编译该扩展后,源代码显示如下:

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
    
      IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
      if (enumerable != null)
        return enumerable;
      if (source == null)
        throw Error.ArgumentNull("source");
      return Enumerable.CastIterator<TResult>(source);
    

    private static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
    
      foreach (TResult result in source)
        yield return result;
    

这基本上除了IEnumerable&lt;object&gt; 之外什么都不做。

你说:

根据我的测试,它适用于参考元素类型,但不适用于 值类型。

你是如何测试的?

【讨论】:

请注意,通过IEnumerable&lt;out T&gt; 的协方差,这意味着如果您有一个IEnumerable&lt;X&gt;,其中X 是一个引用类型,那么.Cast&lt;object&gt;() 将让同一个集合实例通过un -改变。 IE。实际上,它将是对基础集合对象的简单引用转换。对于值类型X.Cast&lt;object&gt;() 的使用必然会贯穿所有项目并将它们装箱(懒惰,就像.Select(x =&gt; (object)x))。【参考方案3】:

尽管我真的不喜欢这种方法,但我知道可以提供一个类似于 LINQ-to-Objects 的工具集,它可以直接在 IEnumerable 接口上调用,而无需强制转换为 IEnumerable&lt;object&gt;(不好:可能的拳击!)并且没有投射到IEnumerable&lt;TFoo&gt;(更糟糕的是:我们需要知道并编写TFoo!)。

但是,它是:

运行时不是免费的:它可能很重,我没有运行性能测试 对开发人员来说不是免费的:您实际上需要为 IEnumerable 编写所有类似 LINQ 的扩展方法(或找到一个库) 不简单:您需要仔细检查传入的类型,并且需要小心许多可能的选项 不是预言机:给定一个实现 IEnumerable 但未实现 IEnumerable&lt;T&gt; 的集合,它只能抛出错误或静默地将其强制转换为 IEnumerable&lt;object&gt; 并不总是有效:给定一个同时实现IEnumerable&lt;int&gt;IEnumerable&lt;string&gt; 的集合,它根本不知道该做什么;即使放弃并投给IEnumerable&lt;object&gt; 听起来也不对

以下是 .Net4+ 的示例:

using System;
using System.Linq;
using System.Collections.Generic;

class Program

    public static void Main()
    
        Console.WriteLine("List<int>");
        new List<int>  1, 2, 3 
            .DoSomething()
            .DoSomething();

        Console.WriteLine("List<string>");
        new List<string>  "a", "b", "c" 
            .DoSomething()
            .DoSomething();

        Console.WriteLine("int[]");
        new int[]  1, 2, 3 
            .DoSomething()
            .DoSomething();

        Console.WriteLine("string[]");
        new string[]  "a", "b", "c" 
            .DoSomething()
            .DoSomething();

        Console.WriteLine("nongeneric collection with ints");
        var stack = new System.Collections.Stack();
        stack.Push(1);
        stack.Push(2);
        stack.Push(3);
        stack
            .DoSomething()
            .DoSomething();

        Console.WriteLine("nongeneric collection with mixed items");
        new System.Collections.ArrayList  1, "a", null 
            .DoSomething()
            .DoSomething();

        Console.WriteLine("nongeneric collection with .. bits");
        new System.Collections.BitArray(0x6D)
            .DoSomething()
            .DoSomething();
    


public static class MyGenericUtils

    public static System.Collections.IEnumerable DoSomething(this System.Collections.IEnumerable items)
    
        // check the REAL type of incoming collection
        // if it implements IEnumerable<T>, we're lucky!
        // we can unwrap it
        // ...usually. How to unwrap it if it implements it multiple times?!
        var ietype = items.GetType().FindInterfaces((t, args) =>
            t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>),
            null).SingleOrDefault();

        if (ietype != null)
        
            return
                doSomething_X(
                    doSomething_X((dynamic)items)
                );
                // .doSomething_X() - and since the compile-time type is 'dynamic' I cannot chain
                // .doSomething_X() - it in normal way (despite the fact it would actually compile well)
               // `dynamic` doesn't resolve extension methods!
               // it would look for doSomething_X inside the returned object
               // ..but that's just INTERNAL implementation. For the user
               // on the outside it's chainable
        
        else
            // uh-oh. no what? it can be array, it can be a non-generic collection
            // like System.Collections.Hashtable .. but..
            // from the type-definition point of view it means it holds any
            // OBJECTs inside, even mixed types, and it exposes them as IEnumerable
            // which returns them as OBJECTs, so..
            return items.Cast<object>()
                .doSomething_X()
                .doSomething_X();
    

    private static IEnumerable<T> doSomething_X<T>(this IEnumerable<T> valitems)
    
        // do-whatever,let's just see it being called
        Console.WriteLine("I got <1>: 0", valitems.Count(), typeof(T));
        return valitems;
    

是的,这很愚蠢。我将它们链接了四次 (2outsidex2inside) 只是为了表明类型信息在后续调用中不会丢失。关键是要表明“入口点”采用非泛型IEnumerable,并且&lt;T&gt; 可以在任何可能的地方解析。您可以轻松地修改代码,使其成为普通的 LINQ-to-Objects .Count() 方法。同样,也可以编写所有其他操作。

此示例使用dynamic 让平台为 IEnumerable 解析最窄的 T,如果可能的话(我们需要首先确保这一点)。如果没有dynamic(即.Net2.0),我们需要通过反射调用dosomething_X,或者将它实现为dosomething_refs&lt;T&gt;():where T:class+dosomething_vals&lt;T&gt;():where T:struct 并做一些魔术来正确调用它而无需实际转换(可能是反射,再次)。

尽管如此,您似乎可以让类似 linq 的东西“直接”处理隐藏在非泛型 IEnumerable 后面的东西。这一切都归功于隐藏在 IEnumerable 后面的对象仍然拥有自己的完整类型信息(是的,COM 或 Remoting 的假设可能会失败)。但是.. 我认为选择IEnumerable&lt;T&gt; 是一个更好的选择。让我们将普通的旧 IEnumerable 留给确实没有其他选择的特殊情况。

..oh.. 我实际上并没有调查上面的代码是否正确、快速、安全、资源节约、惰性评估等。

【讨论】:

哇~我肯定很惊讶。 (@_@)【参考方案4】:

IEnumerable&lt;T&gt; 是一个通用接口。只要您只处理编译时已知的泛型和类型,使用IEnumerable&lt;object&gt; 就没有意义 - 使用IEnumerable&lt;int&gt;IEnumerable&lt;T&gt;,完全取决于您是否正在编写泛型方法,或者一个已经知道正确类型的地方。不要试图找到适合所有这些的 IEnumerable - 首先使用正确的 - 这是不可能的,这是非常罕见的,而且大多数情况下,这只是糟糕的对象设计的结果。

IEnumerable&lt;int&gt; 不能转换为 IEnumerable&lt;object&gt; 的原因可能有点令人惊讶,但实际上非常简单——值类型不是多态的,因此它们不支持协变。不要误会 - IEnumerable&lt;string&gt; 没有实现 IEnumerable&lt;object&gt; - 你可以转换 IEnumerable&lt;string&gt;IEnumerable&lt;object&gt; 的唯一原因是 IEnumerable&lt;T&gt; 是 co -变体。

这只是一个“令人惊讶但显而易见”的有趣案例。令人惊讶的是,int 源自 object,对吧?然而,很明显,因为int 并不是真正 派生自object,即使它可以通过称为装箱的过程转换object ,它创建了一个“真正的对象派生的 int”。

【讨论】:

我必须使用IEnumerable&lt;object&gt;,因为我需要支持不同的元素类型(即运行时多态)。 @Lingxi 如果是这种情况,请使用IEnumerable&lt;object&gt;作为最后的手段,并通过.Cast&lt;object&gt;获得它。对于引用类型,它将执行简单的强制转换(因为它们是协变的),对于值类型,它将执行与 IEnumerable 相同的操作。

以上是关于从 IEnumerable 转换为 IEnumerable<object>的主要内容,如果未能解决你的问题,请参考以下文章

从 IEnumerable 转换为 IEnumerable<object>

无法从“System.Collections.IList”转换为“System.Collections.Generic.IEnumerable<T>”

将数组转换为 IEnumerable<T>

带有 IEnumerable<child> 成员的 ViewModel 在 POST 上未正确绑定 [重复]

无法将类型“IEnumerable<T>”隐式转换为“ActionResult<IEnumerable<T>>”

如何将 JSON 字符串转换为 C# IEnumerable<JToken>