3

我正在实现一个包装项目数组的类,为了方便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<T>”。存在显式转换(您是否缺少演员表?)

为了克服这个问题,我在本地将数组转换为继承的接口,IEnumerable<T>例如IList<T>

  public IEnumerator<T> GetEnumerator() { 
    return ((IList<T>)_items).GetEnumerator();
  }

这编译成功,我的初始测试还表明该类正确地用作可枚举集合。

然而,铸造方法并不完全令人满意。这种方法有什么注意事项吗?能否以更可靠(类型安全)的方式解决问题?

4

3 回答 3

2

这很好,但你可以改为IEnumerable<T>改为,因为那是实际定义的类型GetEnumerator()。将数组强制转换为 an 的最大问题IList<T>是许多变异方法(AddRemove等)会抛出异常。由于您的投射范围非常有限,因此这些问题不会影响您。

于 2013-10-30T17:24:55.100 回答
1

这样做似乎更好:

return ((IEnumerable<T>)_items).GetEnumerator();

作为数组实现IEnumerable<T>

[...] 这种类型实现IEnumerableIEnumerable<T>

您的第一种方法不起作用只是因为 和 之间的IEnumerable.GetEnumerator歧义IEnumerable<T>.GetEnumerator

这里解释了为什么在 IDE 中看不到它的问题:

从 .NET Framework 2.0 开始,Array 类实现了System.Collections.Generic.IList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IEnumerable<T>泛型接口。这些实现在运行时提供给数组,因此对文档构建工具不可见。因此,泛型接口不会出现在 Array 类的声明语法中,并且没有接口成员的参考主题,只能通过将数组转换为泛型接口类型(显式接口实现)才能访问这些成员。将数组强制转换为这些接口之一时要注意的关键是添加、插入或删除元素的成员 throw NotSupportedException

于 2013-10-30T17:25:32.473 回答
0

和原来的问题没有直接关系,但是经过上面的讨论,我对循环性能做了一些实验。

这是一个测试类。它设置了一个包含 10000 个整数的数组,然后使用不同的循环将所有整数连接成一个字符串。

[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 多倍!

底线:出于性能原因,尽可能使用您拥有的特定接口。接口变得越通用,直接访问相关数据的方法就越少。

于 2013-10-31T07:59:57.697 回答