我更喜欢使用IEnumerable<object>
,因为它上面定义了LINQ扩展方法,而不是IEnumerable
,因此我可以使用range.Skip(2)
。但是,我也更喜欢使用IEnumerable
,因为T[]
可隐式转换为IEnumerable
,T
是引用类型还是值类型。对于后一种情况,不涉及拳击,这是好的。结果,我可以做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);
}
}
答案 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<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”。