从IEnumerable转换为IEnumerable <object>

时间:2016-07-19 07:21:59

标签: c# arrays linq ienumerable language-lawyer

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

this SO帖子开始,我知道range.Cast<object>()能够完成这项工作。但它会产生性能开销,这在我看来是不必要的。我尝试执行像(IEnumerable<object>)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);
    }
}

4 个答案:

答案 0 :(得分:8)

  

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

正确。这是因为IEnumerable<out T>是共变体,co-variance/contra-variance is not supported for value types

  

我知道range.Cast()能够完成这项工作。但它   导致性能开销,这在我看来是不必要的。

IMO如果你想要一组带有一系列值类型的对象,那么性能成本(由拳击带来)是不可避免的。使用非通用IEnumerable会避免装箱,因为IEnumerable.GetEnumerator提供了IEnumerator .Current属性返回object的{​​{1}}。我更喜欢始终使用IEnumerable<T>而不是IEnumerable。所以只需使用.Cast方法并忘记拳击。

答案 1 :(得分:1)

尽管我真的不喜欢这种方法,但我知道有可能提供一个类似于LINQ-to-Objects的工具集,可以直接在<tbody> <template ngFor let-service_rec [ngForOf]="list.servicelist"> <tr *ngFor= "let service_rec of list.storagelist"> <td> {{ service_rec.name }} </td> <td> {{ service_rec.status }}</td> <td>{{ service_rec.total | byteFormat }}</td> <td>{{ service_rec.used | byteFormat }}</td> <td>{{ service_rec.free | byteFormat }}</td> </tr> </template> </tbody> 接口上调用,而无需强制转换为{{1} }(糟糕:可能的拳击!)并且没有投射到IEnumerable(更糟糕的是:我们需要知道并编写TFoo!)。

然而,它是:

  • 运行时不自由:可能很重,我没有运行性能测试
  • 对于开发人员来说不是免费的:你实际上需要为IEnumerable编写所有类似LINQ的扩展方法(或找到一个可以执行它的lib)
  • 不简单:您需要仔细检查传入类型,并且需要注意许多可能的选项
  • 不是oracle:给定一个实现IEnumerable但没有实现IEnumerable<object>的集合,它只能抛出错误或者无声地将其转换为IEnumerable<TFoo>
  • 并不总是有效:给定一个同时实现IEnumerable<T>IEnumerable<object>的集合,它根本无法知道该怎么做;甚至放弃并投射到IEnumerable<int>听起来并不合适

这是.Net4 +的一个例子:

IEnumerable<string>

是的,这太傻了。我将它们链接了四次(2个外侧x2内)时间,以便在后续调用中显示类型信息不丢失。关键是要表明“进入点”的关键点。采用非泛型IEnumerable<object>并且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; } } 在任何地方都可以得到解决。您可以轻松地调整代码,使其成为正常的LINQ到对象IEnumerable方法。同样,人们也可以编写所有其他操作。

此示例使用<T>让平台为IEnumerable解析最窄的T,如果可能的话(我们需要首先确保)。如果没有.Count()(即.Net2.0),我们需要通过反射来调用dynamic,或者将其作为dynamic + dosomething_X实施两次并做一些魔术在没有实际投射的情况下正确调用它(可能再次反射)。

尽管如此,似乎你可以直接使用某种东西 - 直接工作&#34;关于非通用IEnumerable背后隐藏的事物。所有这一切都归功于隐藏在IEnumerable后面的对象仍然拥有自己的完整类型信息(是的,这种假设可能会因COM或Remoting而失败)。但是..我认为安顿dosomething_refs<T>():where T:class是一个更好的选择。让我们将原来的dosomething_vals<T>():where T:struct留给特殊情况,但实际上没有其他选择。

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

答案 2 :(得分:0)

在反编译该扩展名后,来源显示了这一点:

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>

你说:

  

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

你是如何测试的?

答案 3 :(得分:0)

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 通过一个名为boxing的进程,它创建了一个“真正的对象派生的int”。