任何警告T []到IList <t>?</t>

时间:2013-10-30 17:21:10

标签: c# arrays linq casting ienumerable

我正在实现一个包装项数组的类,为了方便 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();
  }

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

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

3 个答案:

答案 0 :(得分:2)

这没关系,但您可以转而使用IEnumerable<T>,因为这是实际定义GetEnumerator()的类型。将数组转换为IList<T>的最大问题是许多变异方法(AddRemove等)将抛出异常。由于您在非常有限的范围内进行投射,因此这些问题不会对您产生影响。

答案 1 :(得分:1)

似乎更好:

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

arrays implement IEnumerable<T>

  

[...]此类型实现IEnumerableIEnumerable<T>

您的第一种方法不起作用仅仅因为IEnumerable.GetEnumeratorIEnumerable<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倍!

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