• c#列举和迭代器


    列举 - Enumeration

    迭代器是一个值序列(集合)上的一个只读且只向前移动的游标。迭代器要么实现了IEnumerator接口,要么实现了IEnumerator<T>接口。

    从技术的角度看,如果一个对象有MoveNext方法以及Current属性,那么我们就可以将其看作一个迭代器。

    我们可以使用foreach语句去迭代一个可列举对象。可迭代的对象其实就是一个序列的逻辑体现。可列举的对象不但自身就是一个游标,而且它还可以生成一个游标迭代自己。因此,可列举的对象有两个特性

    • 实现IEnumerator接口,或实现IEnumerator<T>接口
    • 有一个方法GetEnumerator,该方法返回一个迭代器

    列举模式:

    class Enumerator
    {
        public IteratorVariableType Current {get {...}}   
        public bool MoveNext() {...}
    }
    
    class Enumerable
    {
        public Enumerator GetEnumerator() {...}
    }
    Enumeration pattern

    为了更好的理解上面的概率和模式,我们来看下面的两个例子

    foreach (char c in "CSharp")
        Console.WriteLine(c);
    Sample 1
    using (var enumerator = "CSharp".GetEnumerator())
    {
        while (enumerator.MoveNext()) {
            Console.WriteLine(enumerator.Current);
        }
    }
    Sample 2

    Sample1采取了foreach这样的高级方式去迭代字符串(因为字符串类实现了CharEnumerator);而Sample2则使用了底层的方式完成对字符串的迭代。 对于Sample我们使用了using语句,这是因为CharEnumerator实现了IDisposable接口,下面的代码显示了CharEnumrator的大部分代码(来自微软官方)

    public sealed class CharEnumerator : IEnumerator, IDisposable
    {
        private String str;
        private int index;
        private char currentElement;
    
        internal CharEnumerator(String str)
        {
            this.str = str;
            this.index = -1;
        }
    
    
        public bool MoveNext()
        {
            if (index < (str.Length - 1))
            {
                index++;
                currentElement = str[index];
                return true;
            }
            else
                index = str.Length;
            return false;
    
        }
    
        public void Dispose()
        {
            if (str != null)
                index = str.Length;
            str = null;
        }
    
    
        public char Current
        {
            get
            {
                return currentElement;
            }
        }
    
        public void Reset()
        {
            currentElement = (char)0;
            index = -1;
        }
    }
    CharEnumerator

    初始化集合

    我们可使用一行语句实例一个可列举的对象。比如:IList<Int> list = new List<int>{1,2,3};编译时,编译器会自动翻译为:

    IList<Int> list = new List<int>();
    list.Add(1);
    list.Add(2);
    list.Add(3);
    Translated Code

    这是因为该列举对象实现了IEnumerable接口,而且还包含了Add方法。
    为了验证此点,我们可以通过查看IL代码的方式来确认:

    IL_0000:  nop
      IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
      IL_0006:  stloc.1
      IL_0007:  ldloc.1
      IL_0008:  ldc.i4.1
      IL_0009:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
      IL_000e:  nop
      IL_000f:  ldloc.1
      IL_0010:  ldc.i4.2
      IL_0011:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
      IL_0016:  nop
      IL_0017:  ldloc.1
      IL_0018:  ldc.i4.3
      IL_0019:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
      IL_001e:  nop
      IL_001f:  ldloc.1
      IL_0020:  stloc.0
      IL_0021:  call       string [mscorlib]System.Console::ReadLine()
    IL Code

    迭代器 - Iterator

    既然foreach可应用于列举,那么一个列举可以生成一个迭代器。很绕口很困惑是吧,我们先来看下面的例子:使用迭代器返回斐波纳契数列

    static IEnumerable<int> Fibonacci(int number)
    { 
        for(int i=0, prevFib=1, curFib=1;i<number;i++)
        {
            yield return prevFib;
            int newFib = prevFib + curFib;
            prevFib = curFib;
            curFib = newFib;
        }
    }
    
    // test
    static void Main(string[] args)
    {
        foreach (int f in Fibonacci(10))
            Console.WriteLine(f);
    
        Console.ReadLine();
    }
    Fibonacci

    请注意,在上面的代码中,我们使用了yield return。那么它和return有什么区别呢?
    return:从方法中返回一个值
    yield return:从当前的迭代器中生成下一个元素。yield语句每执行一次,程序的控制权就退还给调用者,而被调用者的状态仍然保留,这就使得方法在调用者列举下一个元素的时候能继续执行。被调用者的状态的生命周期取决于列举,正因为如此,当调用者完成列举后,被调用者的状态得以释放。

    迭代器语法

    迭代器可以是包含了一个或多个yield语句的方法、属性、或所引器。迭代器必须返回下面四个类型之一:IEnumerable, IEnumerable<T>, IEnumerator, IEnumerator<T>

    再继续下一步之前,我们看一下IEnumerable接口和IEnumerator的定义

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

    迭代器与列举有不一样的语法,在于迭代器需要返回可列举的接口或者列举器接口。

    创建序列

    迭代器可以进一步用于创建迭代。为了证实这点,我们可以扩展我们斐波纳契数列例子

    static IEnumerable<int> Fibonacci(int number)
    { 
        for(int i=0, prevFib=1, curFib=1;i<number;i++)
        {
            yield return prevFib;
            int newFib = prevFib + curFib;
            prevFib = curFib;
            curFib = newFib;
        }
    }
    
    static IEnumerable<int> EvenNumbers(IEnumerable<int> sequence)
    {
        foreach (int x in sequence)
            if (x % 2 == 0)
                yield return x;
    }
    
    static void Main(string[] args)
    {
        foreach (int f in EvenNumbers(Fibonacci(8)))
            Console.WriteLine(f);
    
        Console.ReadLine();
    }
    Composable Iterator

    请注意,直到Fibonacci方法所产生的数列的MoveNext()方法被调用时(执行foreach循环,会隐式地调用IEnumerator的MoveNext方法),才会判断该元素是否为偶数。

    迭代器可以进一步用于创建迭代大量应用于LINQ。

  • 相关阅读:
    mysql操作规范
    在线修改大表结构pt-online-schema-change
    MySQL 过滤复制
    IDEA “Cannot resolve symbol” 解决办法
    JAVA NIO Selector Channel
    Mysql二级索引
    技术选型关于redis客户端选择
    知乎上看到的一篇讲解Epoll的文章,较形象生动
    linux安装运行virtuoso数据库的详细过程
    go安装步骤(linux和Windows)
  • 原文地址:https://www.cnblogs.com/yang_sy/p/3569541.html
Copyright © 2020-2023  润新知