从 IEnumerable 转换为 IEnumerable

本文关键字:IEnumerable object 转换 | 更新日期: 2023-09-27 17:57:40

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

从这个SO线程中,我了解到range.Cast<object>()能够完成这项工作。但它会产生性能开销,在我看来这是不必要的。我尝试执行像(IEnumerable<object>)range这样的直接编译时强制转换。根据我的测试,它适用于引用元素类型,但不适用于值类型。有什么想法吗?

仅供参考,问题源于这个 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 转换为 IEnumerable<object>

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

正确。这是因为IEnumerable<out T>是协变的,并且值类型不支持协方差/逆方差。

我开始知道这个范围。Cast(( 能够完成这项工作。但它 产生性能开销,在我看来这是不必要的。

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

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

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<object>之外什么也做不了。

你说:

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

您是如何测试的?

尽管我真的不喜欢这种方法,但我知道可以提供类似于 LINQ-to-Objects 的工具集,该工具集可以直接在 IEnumerable 接口上调用,而无需强制强制转换到IEnumerable<object>(糟糕:可能的装箱!(和不强制转换为IEnumerable<TFoo>(更糟糕的是:我们需要知道并编写 TFoo!(。

但是,它是:

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

下面是 的示例。净4+:

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,并且<T>在任何地方都可以解决。您可以轻松调整代码,使其成为正常的 LINQ 到对象.Count()方法。同样,也可以编写所有其他操作。

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

尽管如此,你似乎可以让像linq这样的东西"直接"处理隐藏在非通用IEnumerable后面的东西。这一切都归功于隐藏在 IEnumerable 后面的对象仍然具有自己的完整类型信息(是的,该假设可能会因 COM 或远程处理而失败(。然而。。我认为满足于IEnumerable<T>是一个更好的选择。让我们把普通的旧IEnumerable留给实际上没有其他选择的特殊情况。

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

IEnumerable<T>是一个

通用接口。只要你只处理编译时已知的泛型和类型,使用IEnumerable<object>就没有意义 - 要么使用 IEnumerable<int> 要么使用 IEnumerable<T> ,这完全取决于您是在编写泛型方法,还是已经知道正确类型的方法。不要试图找到一个 IEnumerable 来适合它们 - 首先使用正确的一个 - 这是不可能的是非常罕见的,而且大多数时候,这只是糟糕的对象设计的结果。

IEnumerable<int>不能转换为IEnumerable<object>的原因可能有些令人惊讶,但实际上非常简单 - 值类型不是多态的,因此它们不支持协方差。不要误会 - IEnumerable<string>没有实现IEnumerable<object> - 您可以将IEnumerable<string>转换为IEnumerable<object>的唯一原因是IEnumerable<T>是协变的。

这只是一个"令人惊讶但显而易见"的有趣案例。这很令人惊讶,因为int源于object,对吧?然而,这是显而易见的,因为int并不是真正object派生出来的,即使它可以通过一个叫做boxing的过程被投射到一个object,这个过程会创建一个"真正的对象派生int"。