我正在实现一个包装项数组的类,为了方便 LINQ ,我希望该类实现IEnumerable<T>
接口。
我实施该课程的第一次“天真”尝试如下:
public class Foo<T> : IEnumerable<T>
{
private readonly T[] _items;
public Foo(T[] items) { _items = items; }
public IEnumerator<T> GetEnumerator() { return _items.GetEnumerator(); } // ERROR
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
但是,这不会编译,因为数组只实现IEnumerable
接口而不是IEnumerable<T>
。编译错误是:
无法将类型'System.Collections.IEnumerator'隐式转换为'System.Collections.Generic.IEnumerator&lt; T&gt;'。存在显式转换(您是否错过了演员?)
为了解决这个问题,我在本地将我的数组转换为继承IEnumerable<T>
的接口,例如IList<T>
:
public IEnumerator<T> GetEnumerator() {
return ((IList<T>)_items).GetEnumerator();
}
这成功编译,我的初始测试也表明该类正确地作为可枚举集合。
然而,铸造方法并不完全令人满意。这种方法有什么警告吗?问题能否以更可靠(类型安全)的方式解决?
答案 0 :(得分:2)
这没关系,但您可以转而使用IEnumerable<T>
,因为这是实际定义GetEnumerator()
的类型。将数组转换为IList<T>
的最大问题是许多变异方法(Add
,Remove
等)将抛出异常。由于您在非常有限的范围内进行投射,因此这些问题不会对您产生影响。
答案 1 :(得分:1)
似乎更好:
return ((IEnumerable<T>)_items).GetEnumerator();
为arrays implement IEnumerable<T>
:
[...]此类型实现
IEnumerable
和IEnumerable<T>
您的第一种方法不起作用仅仅因为IEnumerable.GetEnumerator
和IEnumerable<T>.GetEnumerator
之间存在歧义。
解释为什么在IDE中无法看到它的问题here:
从.NET Framework 2.0开始,Array类实现
System.Collections.Generic.IList<T>
,System.Collections.Generic.ICollection<T>
和System.Collections.Generic.IEnumerable<T>
通用接口。这些实现在运行时提供给数组,因此文档构建工具不可见。因此,通用接口不会出现在Array类的声明语法中,并且没有可通过将数组转换为通用接口类型(显式接口实现)来访问的接口成员的参考主题。将数组转换为其中一个接口时要注意的关键是添加,插入或删除元素的成员抛出NotSupportedException
。
答案 2 :(得分:0)
与原始问题没有直接关系,但经过上述讨论后,我对循环性能进行了一些实验。
这是一个测试类。它设置一个10000 int的数组,然后使用不同的循环将所有int连接到一个字符串。
[TestFixture]
class LinqPerformanceTest
{
private List<int> m_intList;
[SetUp]
public void SetUp()
{
m_intList = new List<int>();
for (int i = 0; i < 10000; i++)
{
m_intList.Add(i);
}
}
[Test]
[Repeat(5000)]
public void LoopWithForeach()
{
StringBuilder b = new StringBuilder();
foreach (int v in m_intList)
{
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
[Test]
[Repeat(5000)]
public void LoopWithFor()
{
StringBuilder b = new StringBuilder();
for (int j = 0; j < m_intList.Count; j++)
{
int v = m_intList[j];
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
[Test]
[Repeat(5000)]
public void LoopWithLinq()
{
StringBuilder b = new StringBuilder();
for (int j = 0; j < m_intList.Count(); j++)
{
int v = m_intList.ElementAt(j);
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
[Test]
[Repeat(100)]
public void LoopWithLinq2()
{
StringBuilder b = new StringBuilder();
var myEnumerator = new MyEnumerableWrapper<int>(m_intList);
for (int j = 0; j < myEnumerator.Count(); j++)
{
int v = myEnumerator.ElementAt(j);
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
private class MyEnumerableWrapper<T> : IEnumerable<T>
{
private readonly IEnumerable<T> m_innerEnum;
public MyEnumerableWrapper(IEnumerable<T> innerEnum)
{
m_innerEnum = innerEnum;
}
public IEnumerator<T> GetEnumerator()
{
return m_innerEnum.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
前两个解决方案(LoopWithForeach()
和LoopWithFor()
)大约在同一时间(在我的计算机上大约7秒),在foreach循环上略有优势。
LoopWithLinq
证明了Count()和ElementAt()Linq函数确实在可能的情况下使用了快捷方式。时间可比,但仍比前两次尝试(约8秒)慢一点。虽然Linq做了一个快捷方式评估,发现底层对象实际上是一个列表而Count()是一个O(1)操作,但由于需要额外的强制转换,这样做更加昂贵。
最后一个实现,LoopWithLinq2
,我有意写了一个不支持直接索引的枚举器,肯定会从顶部开始。仅100次迭代需要91秒,这比慢500倍!
底线:出于性能原因,尽可能使用您拥有的特定接口。接口越通用,直接访问相关数据的方法就越少。