C# 枚举器与迭代器始末|详解IEnumerator、 IEnumerable、yield、foreach与迭代器模式

4 篇文章 0 订阅
1 篇文章 0 订阅

前言少叙

也许一切该从 foreach 说起。使用 foreach 遍历一个集合,在应用程序中是不可或缺的。
在 C# 中,可以使用 foreach 的基本类包括(但不限于):字符串(String)、数组(Array)、列表(List)、字典(Dictionary<TKey, TValue>)、队列(Queue)、堆栈(Stack)、集合(HashSet)、链表(LinkedList)等。
这些类之所以可以使用 foreach,是因为它们实现了 IEnumerable 接口,也被称为可枚举对象。IEnumerable 接口中的方法 IEnumerator GetEnumerator(); 返回一个枚举器。这一过程符合设计模式中的行为型——迭代器模式,使用枚举器和可枚举对象的组合实现集合的迭代。因此,迭代器可以看作是枚举器和可枚举对象的组合。
本文将详细介绍上述内容,并提供贴合实际应用的示例代码。
需要注意的是,IEnumerator 和 IEnumerable 是 IEnumerator 和 IEnumerable 的泛型版本,以下内容中不严格区分它们。

1. IEnumerator 与 枚举器

IEnumerator是一个接口,它表示一个集合的枚举器。

public interface IEnumerator
{
    bool MoveNext();
    Object Current { get; }
    void Reset();
}

它提供两个方法和一个只读属性。

  • bool MoveNext(): 将枚举器推进到集合的下一个元素。如果还有更多的元素,返回true,否则返回false
  • void Reset(): 将枚举器的位置重置到集合的开始位置。
  • object Current: 获取当前元素。

工作原理

  1. 初始状态:枚举器在集合的第一个元素之前。调用 MoveNext() 将枚举器移动到集合的第一个元素。
  2. 迭代过程:每次调用 MoveNext(),枚举器都会前进到下一个元素。如果到达集合的末尾,MoveNext() 返回 false
  3. 访问元素:通过 Current 属性访问当前元素。需要注意的是,在调用 MoveNext() 之前或在 MoveNext() 返回 false 之后访问 Current 会引发异常。
  4. 重置:调用 Reset() 将枚举器重新设置到集合的第一个元素之前。

2. IEnumerable 与 可枚举对象

IEnumerable是一个接口,它表示一个集合可以枚举。换句话说,如果一个类实现了IEnumerable接口,那么它就可以被迭代,也就是可枚举对象。

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

这个方法返回一个IEnumerator对象,用于实际的迭代操作。

3. 使用IEnumerator<in T>、IEnumerable<in T> 实现正向或反向遍历的迭代器

如下代码BidirectionalEnumerator枚举器决定怎样枚举,BidirectionalEnumerable 决定怎样使用枚举器

using System;
using System.Collections;
using System.Collections.Generic;

public class BidirectionalEnumerable<T> : IEnumerable<T>
{
    private T[] items; // 存储元素的数组
    private bool reverse; // 是否反向遍历

    public BidirectionalEnumerable(T[] items, bool reverse = false)
    {
        this.items = items; // 初始化数组
        this.reverse = reverse; // 初始化遍历方向
    }

    // 非泛型版本的GetEnumerator方法
    public IEnumerator GetEnumerator()
    {
        return new BidirectionalEnumerator(items, reverse);
    }

    // 泛型版本的GetEnumerator方法
    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return new BidirectionalEnumerator(items, reverse);
    }

    // 内部类,实现了IEnumerator<T>接口
    private class BidirectionalEnumerator : IEnumerator<T>
    {
        private T[] items; // 存储元素的数组
        private int currentIndex; // 当前索引
        private bool reverse; // 是否反向遍历

        public BidirectionalEnumerator(T[] items, bool reverse)
        {
            this.items = items; // 初始化数组
            this.reverse = reverse; // 初始化遍历方向
            this.currentIndex = reverse ? items.Length : -1; // 根据遍历方向初始化索引
        }

        // 移动到下一个元素
        public bool MoveNext()
        {
            if (reverse)
            {
                currentIndex--; // 反向遍历时,索引减1
                return currentIndex >= 0; // 检查是否到达数组开头
            }
            else
            {
                currentIndex++; // 正向遍历时,索引加1
                return currentIndex < items.Length; // 检查是否到达数组末尾
            }
        }

        // 重置枚举器
        public void Reset()
        {
            currentIndex = reverse ? items.Length : -1; // 根据遍历方向重置索引
        }

        // 获取当前元素
        public T Current
        {
            get { return items[currentIndex]; }
        }

        // 显式接口实现,获取当前元素
        object IEnumerator.Current => Current;

        // 释放资源
        public void Dispose()
        {
            // 在这个实现中没有需要释放的资源
        }
    }
}



使用:


// 使用示例
public class Program
{
    public static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };

        // 正向遍历
        var forwardEnumerable = new BidirectionalEnumerable<int>(numbers);
        Console.WriteLine("正向遍历:");
        foreach (var number in forwardEnumerable)
        {
            Console.WriteLine(number);
        }

        // 反向遍历
        var reverseEnumerable = new BidirectionalEnumerable<int>(numbers, true);
        Console.WriteLine("反向遍历:");
        foreach (var number in reverseEnumerable)
        {
            Console.WriteLine(number);
        }
    }
}
输出结果:
正向遍历:
1
2
3
4
5
反向遍历:
5
4
3
2
1



4. 设计模式-迭代器模式Iterator Pattern

使用IEnumerator<in T>、IEnumerable<in T> 正向或反向遍历的迭代器,实际上就遵循了迭代器模式(Iterator Pattern)。

迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供了一种方法来顺序访问一个聚合对象中的各个元素,而不暴露其内部的表示。

1. 意图

迭代器模式的主要目的是:

  • 提供一种统一的方法 来遍历不同的聚合对象。
  • 隐藏聚合对象的内部表示,使得外部代码可以透明地访问集合内部数据。

2. 主要角色

迭代器模式包含以下几个主要角色:

«interface»
Iterator
+bool hasNext()
+Object next()
«interface»
Container
+Iterator getIterator()
NameRepository
+String[] names
+Iterator getIterator()
NameIterator
-int index
+bool hasNext()
+Object next()
IteratorPatternDemo
+Main(String[] args)

2.1 迭代器接口(Iterator)——对应IEnumerator<in T>

定义了访问和遍历聚合对象中各个元素的方法,通常包括以下方法:

  • hasNext():判断是否还有下一个元素。
  • next():返回下一个元素。
//迭代器接口(Iterator)
public interface Iterator
{
    bool hasNext();
    Object next();
}

2.2 具体迭代器(Concrete Iterator)——对应IEnumerable<in T>

实现了迭代器接口,负责对聚合对象进行遍历和访问,同时记录遍历的当前位置。

//定义了创建迭代器对象的接口,通常包括一个工厂方法用于创建迭代器对象。
public interface Container
{
    Iterator getIterator();
}

2.3 聚合对象接口(Aggregate)——对应 IEnumerable<in T>实现了对应可枚举对象

定义了创建迭代器对象的接口,通常包括一个工厂方法用于创建迭代器对象。

public class NameRepository : Container
{
    public String[] names = {"Robert", "John", "Julie", "Lora"};

    public Iterator getIterator()
    {
        return new NameIterator();
    }

    private class NameIterator : Iterator
    {
        int index;

        public bool hasNext()
        {
            return index < names.Length;
        }

        public Object next()
        {
            if (this.hasNext())
            {
                return names[index++];
            }
            return null;
        }
    }
}

2.4 具体聚合对象(Concrete Aggregate)——对应使用Demo

实现了聚合对象接口,负责创建具体的迭代器对象,并提供需要遍历的数据。

public class IteratorPatternDemo
{
    public static void Main(string[] args)
    {
        NameRepository namesRepository = new NameRepository();

        for (Iterator iter = namesRepository.getIterator(); iter.hasNext();)
        {
            String name = (String) iter.next();
            Console.WriteLine("Name : " + name);
        }
    }
}

3. 优缺点

3.1 优点

  • 支持多种遍历方式:不同的迭代器可以定义不同的遍历方式。
  • 简化聚合类:聚合类不需要关心遍历逻辑。
  • 多遍历支持:可以同时对同一个聚合对象进行多次遍历。
  • 扩展性:增加新的聚合类和迭代器类都很方便,无需修改现有代码。

3.2 缺点

  • 系统复杂性:每增加一个聚合类,就需要增加一个对应的迭代器类,增加了类的数量。

4. foreachIEnumerator<in T>、IEnumerable<in T>

“Whereas a foreach statement is the consumer of the enumerator, an iterator is the producer of the enumerator.”
“foreach 语句是枚举器的使用者,而迭代器是枚举器的生产者。” 《C# 5.0 In A NutShell》

可以从简单的foreach示例来看,foreach到底做了什么

示例1:

foreach (var item in collection)
{
    Console.WriteLine(item?.ToString());
}

编译器生成的确切代码更复杂一些,也更能清楚看到foreach到底都干了些什么。

{
    var enumerator = collection.GetEnumerator();
    try
    {
        while (enumerator.MoveNext())
        {
            var item = enumerator.Current;
            Console.WriteLine(item.ToString());
        }
    }
    finally
    {
        // dispose of enumerator.
    }
}

可以看到foreach 语句使用 MoveNext() 和 IEnumerator 的 Current 属性来迭代序列。

扩展

如果遵循迭代器模式:提供一个GetEnumerator方法。这个方法返回一个包含CurrentMoveNextReset的类或结构体。即使并没有实现IEnumerable<in T >IEnumerator<in T >接口, 也可以支持foreach语句。

Demo

using System;

public class MyCollection
{
    private int[] _items = new int[10];

    public MyCollection()
    {
        for (int i = 0; i < _items.Length; i++)
        {
            _items[i] = i * 2;
        }
    }

    // 自定义迭代器类
    public class MyIterator
    {
        private readonly MyCollection _collection;
        private int _index;

        public MyIterator(MyCollection collection)
        {
            _collection = collection;
            _index = -1;
        }

        public bool MoveNext()
        {
            _index++;
            return _index < _collection._items.Length;
        }

        public int Current => _collection._items[_index];
    }

    // 返回一个迭代器实例
    public MyIterator GetEnumerator()
    {
        return new MyIterator(this);
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyCollection myCollection = new MyCollection();
        foreach (var item in myCollection)
        {
            Console.WriteLine(item);
        }
    }
}

输出结果:

0
2
4
6
8
10
12
14
16
18

总结

1 . foreach使用 了枚举器提供的方法,而枚举器由迭代器来提供
2. 在C#中的最佳实践是在集合类中实现IEnumerable<in T >IEnumerator<in T >接口是最佳实践,这样可以启用foreach语法来遍历集合。但并不是必须。

5. yield 语句与迭代器

yield 关键字使方法成为迭代器,。用于在提供下一个值或信号迭代结束。它有两种形式:

  • yield return:提供迭代中的下一个值。
  • yield break:显式信号迭代结束。

1. 使用示例

以下是一个方法示例,它会生成一个序列中的所有偶数,直到遇到一个特定的停止条件。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        foreach (var number in GenerateEvenNumbers(20))
        {
            Console.WriteLine(number);
        }
    }

    static IEnumerable<int> GenerateEvenNumbers(int max)
    {
        for (int i = 0; i <= max; i++)
        {
            if (i % 2 == 0)
            {
                yield return i; // 返回偶数
            }

            if (i > 10)
            {
                yield break; // 当i大于10时停止生成
            }
        }
    }
}

在这个示例中:

  • yield return 用于返回序列中的偶数。
  • yield break 用于在i大于10时停止生成序列。

这个方法会生成0到10之间的所有偶数,并在i大于10时停止。

输出

0
2
4
6
8
10

2. yield 的工作原理

当调用迭代器方法时,它不会立即执行代码,而是返回一个实现了 IEnumerableIEnumerator 接口的对象。

每次调用 MoveNext 方法时,迭代器方法会继续执行,直到遇到 yield returnyield break,迭代器的执行会暂停。

调用方会获得第一个迭代值并处理该值。

在后续的每次迭代中,迭代器的执行都会在导致上一次挂起的 yield return 语句之后恢复,并继续执行,直到到达下一个 yield return 语句为止。

当控件到达迭代器或 yield break 语句的末尾时,迭代完成。

3. yield 的优点

  • 简化代码:使用 yield 可以避免手动实现 IEnumerableIEnumerator 接口。
  • 延迟执行yield 使得迭代器方法可以延迟执行,直到实际需要元素时才生成它们。
  • 状态管理yield 使得迭代器方法可以在每次迭代时保持状态。

4. 注意事项

  • 不能在带有 inrefout 参数的方法中使用 yield
  • 不能在匿名方法或 lambda 表达式中使用 yield
  • 不能在不安全代码块中使用 yield

6. 枚举器实际应用场景及Demo

C# 中的 Enumerator(枚举器)在实际开发中有很多应用场景。以下是一些常见的例子:

1. 自定义集合与自定义迭代方式

在处理复杂的数据结构,如树和图时,标准遍历方法可能不足以满足需求。通过自定义枚举器,可以按照特定逻辑(如深度优先搜索DFS或广度优先搜索BFS)来遍历这些结构。

  1. 定义一个简单的树节点类:

    public class TreeNode<T>
    {
        public T Value { get; set; }
        public List<TreeNode<T>> Children { get; set; }

        public TreeNode(T value)
        {
            Value = value;
            Children = new List<TreeNode<T>>();
        }
    }
  1. 实现一个自定义枚举器来进行深度优先搜索:

    public class DepthFirstEnumerator<T> : IEnumerator<T>
    {
        private readonly TreeNode<T> _root;
        private Stack<TreeNode<T>> _stack;
        private TreeNode<T> _current;

        public DepthFirstEnumerator(TreeNode<T> root)
        {
            _root = root;
            _stack = new Stack<TreeNode<T>>();
            _stack.Push(_root);
        }

        public T Current => _current.Value;

        object IEnumerator.Current => Current;

        public bool MoveNext()
        {
            if (_stack.Count == 0)
                return false;

            _current = _stack.Pop();

            for (int i = _current.Children.Count - 1; i >= 0; i--)
            {
                _stack.Push(_current.Children[i]);
            }

            return true;
        }

        public void Reset()
        {
            _stack.Clear();
            _stack.Push(_root);
        }

        public void Dispose()
        {
            // No resources to dispose
        }
    }
  1. 在树结构上使用这个枚举器:
public class Tree<T> : IEnumerable<T>
        {
            private readonly TreeNode<T> _root;

            public Tree(T rootValue)
            {
                _root = new TreeNode<T>(rootValue);
            }

            public TreeNode<T> Root => _root;

            public IEnumerator<T> GetEnumerator()
            {
                return new DepthFirstEnumerator<T>(_root);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
  1. 测试示例:
namespace TreeNodeTests
{
    [TestClass]
    public class TreeNodeTests
    {
        [TestMethod]
        public void TestTreeCreation()
        {
            var tree = new Tree<int>(1);
            Assert.AreEqual(1, tree.Root.Value);
            Assert.AreEqual(0, tree.Root.Children.Count);
        }

        [TestMethod]
        public void TestDepthFirstEnumerator()
        {
            var root = new TreeNode<int>(1);
            root.Children.Add(new TreeNode<int>(2));
            root.Children.Add(new TreeNode<int>(3));
            root.Children[0].Children.Add(new TreeNode<int>(4));
            root.Children[0].Children.Add(new TreeNode<int>(5));
            root.Children[1].Children.Add(new TreeNode<int>(6));

            var enumerator = new DepthFirstEnumerator<int>(root);
            var expectedValues = new List<int> { 1, 2, 4, 5, 3, 6 };
            var actualValues = new List<int>();

            while (enumerator.MoveNext())
            {
                actualValues.Add(enumerator.Current);
            }

            CollectionAssert.AreEqual(expectedValues, actualValues);
        }
    }
}

2. 延迟计算与大量数据处理

使用yield关键字可以创建一个延迟计算的枚举器,这在处理大量数据,分页数据,或复杂计算时,枚举器可以逐页或逐行提供数据,从而减少内存消耗并提高效率。

  • 计算素数

例如,生成素数的序列时,可以逐个计算素数,而不是一次性计算所有素数。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        //foreach 循环来逐个获取素数并打印出来
        foreach (var prime in GeneratePrimes(50))
        {
            Console.WriteLine(prime);
        }
    }

    static IEnumerable<int> GeneratePrimes(int max)
    {
        for (int number = 2; number <= max; number++)
        {
            if (IsPrime(number))//
            {
                yield return number;
            }
        }
    }
    //检查一个数是否为素数
    static bool IsPrime(int number)
    {
        if (number < 2) return false;
        for (int i = 2; i <= Math.Sqrt(number); i++)
        {
            if (number % i == 0) return false;
        }
        return true;
    }
}

输出

2
3
5
7
11
13
17
19
23
29
31
37
41
43
47



  • 数据分页
    通过延迟计算,可以在请求下一页数据时才进行计算和加载,而不是一次性加载所有数据。
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        foreach (var item in GetPagedData(100, 10))
        {
            Console.WriteLine(item);
        }
    }

    static IEnumerable<int> GetPagedData(int totalItems, int pageSize)
    {
        for (int i = 0; i < totalItems; i += pageSize)
        {
            for (int j = i; j < i + pageSize && j < totalItems; j++)
            {
                yield return j;
            }
            Console.WriteLine("Press any key to load next page...");
            Console.ReadKey();
        }
    }
}
  • 文件读取
    使用延迟计算逐行读取文件内容,而不是一次性读取整个文件。
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
    static void Main()
    {
        foreach (var line in ReadLines("largefile.txt"))
        {
            Console.WriteLine(line);
        }
    }

    static IEnumerable<string> ReadLines(string filePath)
    {
        using (StreamReader reader = new StreamReader(filePath))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }
}

3. LINQ查询

通过自定义迭代器和扩展方法,可以扩展LINQ查询功能,实现更复杂的数据处理逻辑。

  1. 自定义迭代器方法GetSingleDigitNumbers 方法使用 yield return 关键字返回从 0 到 9 的整数序列。
  2. 扩展方法Median 扩展方法计算整数序列的中位数。它首先对序列进行排序,然后根据元素来计算中位数。
  3. 主程序:在 Main 方法中,调用自定义迭代器获取数字序列,并使用扩展方法计算中位数,最后将结果输出到控制台。
using System;
using System.Collections.Generic;
using System.Linq;

namespace CustomIteratorDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 使用自定义迭代器获取单个数字序列
            var numbers = GetSingleDigitNumbers();

            // 使用扩展方法计算中位数
            var median = numbers.Median();
            Console.WriteLine($"Median: {median}");
        }

        // 自定义迭代器方法,返回从 0 到 9 的整数序列
        public static IEnumerable<int> GetSingleDigitNumbers()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i;
            }
        }
    }

    // 扩展方法类
    public static class EnumerableExtensions
    {
        // 扩展方法,计算序列的中位数
        public static double Median(this IEnumerable<int> source)
        {
            if (source == null || !source.Any())
            {
                throw new InvalidOperationException("Cannot compute median for a null or empty set.");
            }

            var sortedList = source.OrderBy(number => number).ToList();
            int count = sortedList.Count;
            if (count % 2 == 0)
            {
                return (sortedList[count / 2 - 1] + sortedList[count / 2]) / 2.0;
            }
            else
            {
                return sortedList[count / 2];
            }
        }
    }
}

输出

Median: 4.5

4. 状态机

状态机是一种用于在不同状态之间进行转换的模型。可以通过 yield return 语句在不同状态之间进行转换。

代码说明
  1. ParserState 枚举定义了解析器的三个状态:读取文本、读取数字和结束。
  2. ParserStateMachine 类实现了 IEnumerable<ParserState> 接口,用于迭代解析器的状态。
  3. GetEnumerator 方法使用 yield return 语句根据输入字符的类型返回当前状态,并更新解析器的位置。
  4. Program 类中的 Main 方法创建一个解析器实例,并使用 foreach 循环迭代解析器的状态。
using System;
using System.Collections;
using System.Collections.Generic;

// 定义解析器状态的枚举
public enum ParserState
{
    ReadingText,  // 读取文本状态
    ReadingNumber,  // 读取数字状态
    End  // 结束状态
}

// 定义解析器状态机类,实现了 IEnumerable<ParserState> 接口
public class ParserStateMachine : IEnumerable<ParserState>
{
    private readonly string _input;  // 输入字符串
    private int _position;  // 当前解析位置


    public ParserStateMachine(string input)
    {
        _input = input;
        _position = 0;
    }

    // 实现 GetEnumerator 方法,返回解析器状态的枚举器
    public IEnumerator<ParserState> GetEnumerator()
    {
        // 遍历输入字符串
        while (_position < _input.Length)
        {
            char currentChar = _input[_position];  // 获取当前字符
            if (char.IsLetter(currentChar))  // 如果是字母
            {
                yield return ParserState.ReadingText;  // 返回读取文本状态
                // 跳过连续的字母
                while (_position < _input.Length && char.IsLetter(_input[_position]))
                {
                    _position++;
                }
            }
            else if (char.IsDigit(currentChar))  // 如果是数字
            {
                yield return ParserState.ReadingNumber;  // 返回读取数字状态
                // 跳过连续的数字
                while (_position < _input.Length && char.IsDigit(_input[_position]))
                {
                    _position++;
                }
            }
            else  // 其他字符
            {
                _position++;  // 跳过
            }
        }
        yield return ParserState.End;  // 返回结束状态
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// 使用Demo
class Program
{
    static void Main(string[] args)
    {
        string input = "Hello123World456";  // 输入字符串
        ParserStateMachine parser = new ParserStateMachine(input);  // 创建解析器状态机实例

        // 遍历解析器状态并输出
        foreach (var state in parser)
        {
            Console.WriteLine($"Current State: {state}");
        }
    }
}


输出
Current State: ReadingText
Current State: ReadingNumber
Current State: ReadingText
Current State: ReadingNumber
Current State: End

5. 异步编程

在异步编程中,IEnumerator可以与asyncawait结合使用,实现异步迭代。例如,可以从异步数据流中逐个读取数据项。

public class DataLoader
{
    // 模拟异步加载数据的方法
    public async IAsyncEnumerable<string> LoadDataAsync()
    {
        List<string> dataSources = new List<string> { "数据源1", "数据源2", "数据源3" };

        foreach (var source in dataSources)
        {
            // 模拟异步加载数据
            await Task.Delay(1000); // 模拟异步操作
            yield return $"从{source}加载的数据";
        }
    }
}

单元测试:

[TestClass]
public class DataLoaderTests
{
    [TestMethod]
    public async Task LoadDataAsync_ShouldReturnCorrectData()
    {
        // Arrange
        var dataLoader = new DataLoader();
        var expectedData = new List<string> { "从数据源1加载的数据", "从数据源2加载的数据", "从数据源3加载的数据" };
        var actualData = new List<string>();

        // Act
        await foreach (var data in dataLoader.LoadDataAsync())
        {
            actualData.Add(data);
        }

        // Assert
        CollectionAssert.AreEqual(expectedData, actualData);
    }
}

总结

  1. 实现**IEnumerator**接口枚举器专门用于遍历集合。它就像一个游标,可以指向当前数据并移动到下一个数据,从而逐个取出集合中的数据,而无需关心其内部的数据结构或地址等细节。
  2. 实现**IEnumerable**接口可枚举对象表示该对象提供了获取枚举器的方法,可以使用此枚举器对集合进行遍历。
  3. 在C#中,通过实现IEnumeratorIEnumerable接口的可枚举对象,可以支持foreach语句,从而简洁地遍历数组或集合。这一过程遵循了迭代器模式。
  4. yield语句:可以用于实现迭代器方法。使用yield可以避免手动实现IEnumerableIEnumerator接口,在延迟执行和状态管理上发挥重要作用。
  5. 异步迭代:可以与asyncawait结合使用,实现异步迭代。
  6. foreach使用 了枚举器提供的方法,而枚举器由迭代器来提供。因此,迭代器可以看作是枚举器和可枚举对象的组合

和IComparable<in T> 与 IComparer 类似,able代表能力,表示实现该接口的类具有XX能力。er名词,表示自定义实现具有XX功能的枚举器

【参考】

  1. 迭代器 - C# | Microsoft Learn
  2. c# - Distinction between iterator and enumerator - Stack Overflow(第19个回答)
  3. 将自己的扩展写入 LINQ - C# | Microsoft Learn
  4. yield 语句 - 在迭代器中提供下一个元素 - C# reference | Microsoft Learn
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值