【问题标题】:When should I use IEnumerator for looping in c#?什么时候应该使用 IEnumerator 在 c# 中循环?
【发布时间】:2010-10-02 04:03:36
【问题描述】:

我想知道是否有任何时候使用 IEnumerator 而不是 foreach 循环来迭代集合是有利的?例如,是否有任何时候使用以下代码示例比另一个更好?

IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator();
while(classesEnum.MoveNext())
    Console.WriteLine(classesEnum.Current);

而不是

foreach (var class in myClasses)
    Console.WriteLine(class);

【问题讨论】:

    标签: c# iterator ienumerable loops


    【解决方案1】:

    首先,请注意,您的示例(foreachGetEnumerator 之间)的一大区别是,如果迭代器是 IDisposableforeach 保证在迭代器上调用 Dispose()。这对于许多迭代器很重要(例如,它们可能正在使用外部数据馈送)。

    实际上,在某些情况下foreach 并没有我们想要的那么有用。

    首先是here (foreach / detecting first iteration)讨论的“第一项”案例。

    还有更多;如果您尝试编写缺少的Zip 方法来将两个可枚举序列拼接在一起(或SequenceEqual 方法),您会发现您不能在两个序列上使用foreach,因为这会执行交叉连接。您需要直接将迭代器用于其中之一:

    static IEnumerable<T> Zip<T>(this IEnumerable<T> left,
        IEnumerable<T> right)
    {
        using (var iter = right.GetEnumerator())
        {
            // consume everything in the first sequence
            foreach (var item in left)
            {
                yield return item;
    
                // and add an item from the second sequnce each time (if we can)
                if (iter.MoveNext())
                {
                    yield return iter.Current;
                }
            }
            // any remaining items in the second sequence
            while (iter.MoveNext())
            {
                yield return iter.Current;
            }                
        }            
    }
    
    static bool SequenceEqual<T>(this IEnumerable<T> left,
        IEnumerable<T> right)
    {
        var comparer = EqualityComparer<T>.Default;
    
        using (var iter = right.GetEnumerator())
        {
            foreach (var item in left)
            {
                if (!iter.MoveNext()) return false; // first is longer
                if (!comparer.Equals(item, iter.Current))
                    return false; // item different
            }
            if (iter.MoveNext()) return false; // second is longer
        }
        return true; // same length, all equal            
    }
    

    【讨论】:

    • 不应该是类似于 while(iter.MoveNext()) 以防右边比左边有两个或更多元素吗?
    • 嗨,马克,很好的答案!您能详细说明“执行交叉连接”是什么意思吗?
    • @Nameless 也许我在滥用 SQL 术语,但通过交叉连接,我只是指两组之间的笛卡尔积
    • @MarcGravell 感谢您的澄清!
    【解决方案2】:

    我有时会在第一次迭代做一些与其他人不同的事情时使用它。

    例如,如果你想打印成员到控制台并按行分隔结果,你可以写。

    using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) {
        if (classEnum.MoveNext())
            Console.WriteLine(classesEnum.Current);
        while (classesEnum.MoveNext()) {
            Console.WriteLine("-----------------");
            Console.WriteLine(classesEnum.Current);
        }
    }
    

    那么结果就是

    myClass 1
    -----------------
    myClass 2
    -----------------
    myClass 3
    

    另一种情况是当我一起迭代 2 个枚举器时。

    using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) {
        using (IEnumerator<MyClass> classesEnum2 = myClasses2.GetEnumerator()) {
            while (classesEnum.MoveNext() && classEnum2.MoveNext())
                Console.WriteLine("{0}, {1}", classesEnum.Current, classesEnum2.Current);
        }
    }
    

    结果

    myClass 1, myClass A
    myClass 2, myClass B
    myClass 3, myClass C
    

    使用枚举器可以让您更灵活地进行迭代。但在大多数情况下,您可以使用 foreach

    【讨论】:

    • 第一次迭代:可以使用foreach( var item in myList.Select(val, index)) 获取当前元素的索引。使用扩展方法,你甚至可以写 foreach(var item in myList.SelectWithIndex())
    【解决方案3】:

    根据C# language spec

    形式的foreach语句

    foreach (V v in x) embedded-statement
    

    然后扩展为:

    {
        E e = ((C)(x)).GetEnumerator();
        try {
                V v;
                while (e.MoveNext()) {
                        v = (V)(T)e.Current;
                        embedded-statement
                }
        }
        finally {
                ... // Dispose e
        }
    }
    

    你的两个例子的本质是一样的,但是有一些重要的区别,最明显的是try/finally

    【讨论】:

    • 很有趣,因为这也显示了可以触发什么样的隐式转换(可选用户定义)
    【解决方案4】:

    在 C# 中,foreach 所做的事情与您在上面发布的 IEnumerator 代码完全相同。提供foreach 构造是为了使程序员更容易。我相信在这两种情况下生成的 IL 是相同/相似的。

    【讨论】:

    • 在某些情况下您需要手动执行 - 请参阅下面的答案。
    • 实际上,在他的示例中,IL 没有相同,因为他没有检查 IDisposable 的迭代器。这对许多迭代器很重要。
    猜你喜欢
    • 2011-11-12
    • 2010-12-30
    • 1970-01-01
    • 2010-10-18
    • 2011-06-16
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    相关资源
    最近更新 更多