简体   繁体   English

从IEnumerable转换为IEnumerable <object>

[英]Cast from IEnumerable to IEnumerable<object>

I prefer to use IEnumerable<object> , for LINQ extension methods are defined on it, not IEnumerable , so that I can use, for example, range.Skip(2) . 我更喜欢使用IEnumerable<object> ,因为它上面定义了LINQ扩展方法,而不是IEnumerable ,所以我可以使用,例如, range.Skip(2) However, I also prefer to use IEnumerable , for T[] is implicitly convertible to IEnumerable whether T is a reference type or value type. 但是,我也更喜欢使用IEnumerable ,因为T[]可以隐式转换为IEnumerable无论T是引用类型还是值类型。 For the latter case, no boxing is involved, which is good. 对于后一种情况,不涉及拳击,这是好的。 As a result, I can do IEnumerable range = new[] { 1, 2, 3 } . 结果,我可以做IEnumerable range = new[] { 1, 2, 3 } It seems impossible to combine the best of both worlds. 似乎不可能将两者的优点结合起来。 Anyway, I chose to settle down to IEnumerable and do some kind of cast when I need to apply LINQ methods. 无论如何,当我需要应用LINQ方法时,我选择安顿到IEnumerable并进行某种演员。

From this SO thread, I come to know that range.Cast<object>() is able to do the job. 这个 SO线程,我开始知道range.Cast<object>()能够完成这项工作。 But it incurs performance overhead which is unnecessary in my opinion. 但它会产生性能开销,这在我看来是不必要的。 I tried to perform a direct compile-time cast like (IEnumerable<object>)range . 我尝试执行像(IEnumerable<object>)range的直接编译时转换。 According to my tests, it works for reference element type but not for value type. 根据我的测试,它适用于参考元素类型,但不适用于值类型。 Any ideas? 有任何想法吗?

FYI, the question stems from this GitHub issue. 仅供参考,问题源于 GitHub问题。 And the test code I used is as follows: 我使用的测试代码如下:

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

According to my tests, it works for reference element type but not for value type. 根据我的测试,它适用于参考元素类型,但不适用于值类型。

Correct. 正确。 This is because IEnumerable<out T> is co-variant, and co-variance/contra-variance is not supported for value types . 这是因为IEnumerable<out T>是共变量,并且值类型不支持协方差/反方差

I come to know that range.Cast() is able to do the job. 我知道range.Cast()能够完成这项工作。 But it incurs performance overhead which is unnecessary in my opinion. 但它会产生性能开销,这在我看来是不必要的。

IMO the performance cost(brought by boxing) is unavoidable if you want a collection of objects with a collection of value-types given. IMO的性能成本(由拳击带来)是不可避免的,如果你想要一个带有一系列值类型的对象的集合。 Using the non-generic IEnumerable won't avoid boxing because IEnumerable.GetEnumerator provides a IEnumerator whose .Current property returns an object . 使用非通用的IEnumerable将无法避免装箱,因为IEnumerable.GetEnumerator提供了一个IEnumerator.Current属性返回一个object I'd prefer always use IEnumerable<T> instead of IEnumerable . 我宁愿总是使用IEnumerable<T>而不是IEnumerable So just use the .Cast method and forget the boxing. 所以只需使用.Cast方法并忘记拳击。

After decompiling that extension, the source showed this: 反编译该扩展后,源显示:

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

This basically does nothing else than IEnumerable<object> in first place. 这基本上除了IEnumerable<object>之外什么也没做。

You stated: 你说:

According to my tests, it works for reference element type but not for value type. 根据我的测试,它适用于参考元素类型,但不适用于值类型。

How did you test that? 你是怎么测试的?

Despite I really do not like this approach, I know it is possible to provide a toolset similar to LINQ-to-Objects that is callable directly on an IEnumerable interface, without forcing a cast to IEnumerable<object> (bad: possible boxing!) and without casting to IEnumerable<TFoo> (even worse: we'd need to know and write TFoo!). 尽管我真的不喜欢这种方法,但我知道有可能提供一个类似于LINQ-to-Objects的工具集,它可以直接在IEnumerable接口上调用,而不会强制转换为IEnumerable<object> (坏:可能是装箱!)并且没有转换为IEnumerable<TFoo> (更糟糕的是:我们需要知道并编写TFoo!)。

However, it is: 但是,它是:

  • not free for runtime: it may be heavy, I didn't run perfomance test 运行时不自由:它可能很重,我没有运行性能测试
  • not free for developer: you actually need to write all those LINQ-like extension methods for IEnumerable (or find a lib that does it) 对开发人员来说不是免费的:你实际上需要为IEnumerable编写所有类似LINQ的扩展方法(或者找一个执行它的lib)
  • not simple: you need to inspect the incoming type carefully and need to be careful with many possible options 不简单:您需要仔细检查传入类型,并且需要注意许多可能的选项
  • is not an oracle: given a collection that implements IEnumerable but does not implement IEnumerable<T> it only can throw error or silently cast it to IEnumerable<object> 不是oracle:给定一个实现IEnumerable但没有实现IEnumerable<T>的集合,它只能抛出错误或者静默地将它转换为IEnumerable<object>
  • will not always work: given a collection that implements both IEnumerable<int> and IEnumerable<string> it simply cannot know what to do; 并不总是有效:给定一个实现IEnumerable<int>IEnumerable<string>的集合,它根本无法知道该怎么做; even giving up and casting to IEnumerable<object> doesn't sound right here 甚至放弃并转换为IEnumerable<object>在这里听起来不合适

Here's an example for .Net4+: 这是.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;
    }
}

Yes, that's silly. 是的,那太傻了。 I chained them four (2outsidex2inside) times just to show that the type information is not lost in subsequent calls. 为了证明类型信息在后续调用中没有丢失 ,我将它们链接了四次(2outsidex2inside)次。 The point was to show that the 'entry point' takes a nongeneric IEnumerable and that <T> is resolved wherever it can be. 关键是要显示'入口点'采用非通用的IEnumerable ,并且<T>在任何地方都可以解析。 You can easily adapt the code to make it a normal LINQ-to-Objects .Count() method. 您可以轻松地调整代码,使其成为正常的LINQ-to-Objects .Count()方法。 Similarly, one can write all other operations, too. 同样,人们也可以编写所有其他操作。

This example uses dynamic to let the platform resolve the most-narrow T for IEnumerable, if possible (which we need to ensure first). 这个例子使用dynamic让平台为IEnumerable解析最窄的T,如果可能的话(我们需要先确保)。 Without dynamic (ie .Net2.0) we'd need to invoke the dosomething_X through reflection, or implement it twice as dosomething_refs<T>():where T:class + dosomething_vals<T>():where T:struct and do some magic to call it properly without actually casting (probably reflection, again). 没有dynamic (即.Net2.0)我们需要调用dosomething_X通过反射,或实现它的两倍dosomething_refs<T>():where T:class + dosomething_vals<T>():where T:struct和做一些魔法可以在没有实际施放的情况下正确地调用它(可能再次反射)。

Nevertheless, it seems that you can get something-like-linq working "directly" on things hidden behind nongeneric IEnumerable. 然而,似乎你可以得到类似linq的东西“直接”在隐藏在非通用IEnumerable背后的东西上。 All thanks to the fact that the objects hiding behind IEnumerable still have their own full type information (yeah, that assumption may fail with COM or Remoting). 所有这一切都归功于隐藏在IEnumerable后面的对象仍然拥有自己的完整类型信息(是的,这种假设可能会因COM或Remoting而失败)。 However.. I think settling for IEnumerable<T> is a better option. 但是..我认为解决IEnumerable<T>是一个更好的选择。 Let's leave plain old IEnumerable to special cases where there is really no other option. 让我们将普通的IEnumerable留给特殊情况,其中没有其他选择。

..oh.. and I actually didn't investigate if the code above is correct, fast, safe, resource-conserving, lazy-evaluating, etc. ..哦..我实际上没有调查上面的代码是否正确,快速,安全,资源保存,懒惰评估等。

IEnumerable<T> is a generic interface. IEnumerable<T>是一个通用接口。 As long as you're only dealing with generics and types known at compile-time, there's no point in using IEnumerable<object> - either use IEnumerable<int> or IEnumerable<T> , depending entirely on whether you're writing a generic method, or one where the correct type is already known. 只要您只处理编译时已知的泛型和类型,使用IEnumerable<object>就没有意义 - 使用IEnumerable<int>IEnumerable<T> ,完全取决于您是否正在编写泛型方法,或已知正确类型的方法。 Don't try to find an IEnumerable to fit them all - use the correct one in the first place - it's very rare for that not to be possible, and most of the time, it's simply a result of bad object design. 不要试图找到一个IEnumerable来适应它们 - 首先使用正确的 - 这是非常罕见的,这是不可能的,而且大多数时候,它只是坏对象设计的结果。

The reason IEnumerable<int> cannot be cast to IEnumerable<object> may be somewhat surprising, but it's actually very simple - value types aren't polymorphic, so they don't support co-variance. IEnumerable<int>无法强制转换为IEnumerable<object>可能有点令人惊讶,但实际上非常简单 - 值类型不是多态的,因此它们不支持协方差。 Do not be mistaken - IEnumerable<string> doesn't implement IEnumerable<object> - the only reason you can cast IEnumerable<string> to IEnumerable<object> is that IEnumerable<T> is co-variant. 不要误会 - IEnumerable<string>实现 IEnumerable<object> - 你可以 IEnumerable<string> IEnumerable<object>IEnumerable<object>的唯一原因是IEnumerable<T>是共变体。

It's just a funny case of "surprising, yet obvious". 这只是一个“令人惊讶但又显而易见”的有趣案例。 It's surprising, since int derives from object , right? 令人惊讶的是,因为int来自于object ,对吧? And yet, it's obvious, because int doesn't really derive from object , even though it can be cast to an object through a process called boxing, which creates a "real object-derived int". 然而,很明显,因为int并不真正派生自object ,即使它可以通过一个名为boxing的进程强制转换为object ,这会创建一个“真正的对象派生的int”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM