23.迭代器

23.迭代器


23.1 知识点

迭代器是什么

迭代器(iterator)有时又称光标(cursor),是程序设计的软件设计模式。迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的标识。

在表现效果上看,迭代器是可以在容器对象(例如链表或数组)上遍历访问的接口。设计人员无需关心容器对象的内存分配的实现细节,可以用foreach遍历的类,都是实现了迭代器的。

标准迭代器的实现方法和使用

class语句块外 namespace语句块内

关键接口:IEnumerator, IEnumerable。命名空间:using System.Collections;。可以通过同时继承IEnumerableIEnumerator 实现其中的方法。

实例:

// 自定义列表 同时继承IEnumerable和IEnumerator
class CustomList : IEnumerable, IEnumerator
{
    // 整形数组
    private int[] intArray;
    
    // 默认从-1开始的光标 用于表示 数据得到了哪个位置
    private int position = -1;
    
    // 构造函数初始化整形数组
    public CustomList()
    {
        intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    }
    
    #region IEnumerable里要实现的成员
    public IEnumerator GetEnumerator()
    {
        // 每次foreach前想重置光标位置
        Reset();
        
        // 自定义列表类 就继承了IEnumerator 直接返回自己自定义列表类就行
        return this;
    }
    #endregion
    
    #region IEnumerator里要实现的成员
    
    // 根据当前光标位置 返回元素
    public object Current
    {
        get
        {
            return intArray[position];
        }
    }
    
    // 移动光标到下一位置
    public bool MoveNext()
    {
        // 移动光标
        ++position;
        // 是否溢出 溢出就不合法
        return position < intArray.Length;
    }
    
    // reset是重置光标位置 一般写在获取 IEnumerator对象这个函数中
    // 用于第一次重置光标位置
    public void Reset()
    {
        position = -1;
    }
    
    #endregion
}

主函数内

foreach本质
  1. 先获取 in 后面这个要遍历的对象的 IEnumerator
  2. 执行得到这个要遍历的对象的 IEnumerator 中的 MoveNext 移动光标方法。
  3. 只要 MoveNext 方法的返回值是 true,就会去得到 Current,否则 MoveNext 方法的返回值是 false,结束 foreach
  4. 然后赋值给 item
  5. 重复2-4 直到 MoveNext 方法的返回值是 false,结束 foreach
创建一个自定义列表实例
CustomList customList1 = new CustomList();
foreach遍历打印customList1中的元素
foreach (int item in customList1)
{
    Console.WriteLine("遍历customList1:" + item);
    // 遍历customList1:1
    // 遍历customList1:2
    // 遍历customList1:3
    // 遍历customList1:4
    // 遍历customList1:5
    // 遍历customList1:6
    // 遍历customList1:7
    // 遍历customList1:8
}

注意:假如没有实现Reset的逻辑,在此foreach遍历打印会打印不出来,因为光标还是在末尾元素,MoveNext为false。

foreach (int item in customList1)
{
    Console.WriteLine("遍历customList1:" + item);
    // 遍历customList1:1
    // 遍历customList1:2
    // 遍历customList1:3
    // 遍历customList1:4
    // 遍历customList1:5
    // 遍历customList1:6
    // 遍历customList1:7
    // 遍历customList1:8
}

用yield return语法糖实现迭代器

class语句块外 namespace语句块内

yield return 是C#提供给我们的语法糖,所谓语法糖,也称糖衣语法。主要作用就是将复杂逻辑简单化,可以增加程序的可读性,从而减少程序代码出错的机会。

关键接口:IEnumerable。命名空间:using System.Collections;。让想要通过foreach遍历的自定义类实现接口中的方法GetEnumerator即可。

实例:

// 自定义列表2 只继承IEnumerable
class CustomList2 : IEnumerable
{
    private int[] intArray2;
    
    public CustomList2()
    {
        intArray2 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    }
    


    #region IEnumerable里要实现的成员
    
    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < intArray2.Length; i++)
        {
            // yield关键字 配合迭代器使用
            // 可以理解为 暂时返回 保留当前的状态
            // 一会还会在回来
            // C#的语法糖
            // 本质上其实还是有 Current属性 MoveNext方法 Reset方法
            // 只是系统检测到yield return关键字 帮我们做好了这部分逻辑
            yield return intArray2[i];
        }
        // 这样逐个写也可以
        // yield return intArray[0];
        // yield return intArray[1];
        // yield return intArray[2];
        // yield return intArray[3];
        // yield return intArray[4];
        // yield return intArray[5];
        // yield return intArray[6];
        // yield return intArray[7];
    }
    
    #endregion
}

主函数内

创建一个自定义列表2实例:

CustomList customList2 = new CustomList();

foreach遍历打印customList2中的元素:

foreach (int item in customList2)
{
    Console.WriteLine("遍历customList2:" + item);
    // 遍历customList2:1
    // 遍历customList2:2
    // 遍历customList2:3
    // 遍历customList2:4
    // 遍历customList2:5
    // 遍历customList2:6
    // 遍历customList2:7
    // 遍历customList2:8
}

用yield return语法糖为泛型类实现迭代器

class语句块外 namespace语句块内

泛型自定义列表,其实还是继承IEnumerable。其他和非泛型大同小异,有泛型的IEnumerable,暂时用不上也可以。

// 泛型自定义列表
// 其实还是继承IEnumerable 其他和非泛型大同小异
// 有泛型的IEnumerable 暂时用不上也可以
class CustomList<T> : IEnumerable
{
    private T[] array;
    
    public CustomList(params T[] array)
    {
        this.array = array;
    }
    
    #region IEnumerable里要实现的成员
    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < array.Length; i++)
        {
            yield return array[i];
        }
    }
    #endregion
}

主函数内

创建一个自定义泛型列表实例,传入字符串类型:

CustomList<string> customListString = new CustomList<string>("123", "321", "333", "555");

foreach遍历打印customListString中的元素:

foreach (string item in customListString)
{
    Console.WriteLine("customListString:" + item);
    // customListString:123
    // customListString:321
    // customListString:333
    // customListString:555
}

总结

迭代器就是可以让我们在外部直接通过foreach遍历对象中元素而不需要了解其结构。主要的两种方式:

  1. 传统方式 继承两个接口 实现里面的方法。
  2. 用语法糖 yield return 去返回内容 只需要继承一个接口即可。

23.2 知识点代码

using System;
using System.Collections;

namespace Lesson22_迭代器
{
    #region 知识点一 迭代器是什么
    //迭代器(iterator)有时又称光标(cursor)
    //是程序设计的软件设计模式
    //迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素
    //而又不暴露其内部的标识

    //在表现效果上看
    //是可以在容器对象(例如链表或数组)上遍历访问的接口
    //设计人员无需关心容器对象的内存分配的实现细节
    //可以用foreach遍历的类,都是实现了迭代器的
    #endregion

    //class语句块外 namespace语句块内

    #region 知识点二 标准迭代器的实现方法和使用

    //关键接口:IEnumerator,IEnumerable
    //命名空间:using System.Collections;
    //可以通过同时继承IEnumerable和IEnumerator 实现其中的方法

    //实例
    //自定义列表 同时继承IEnumerable和IEnumerator
    class CustomList : IEnumerable, IEnumerator
    {
        //整形数组
        private int[] intArray;

        //默认从-1开始的光标 用于表示 数据得到了哪个位置
        private int position = -1;

        //构造函数初始化整形数组
        public CustomList()
        {
            intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        }

        #region IEnumerable里要实现的成员
        public IEnumerator GetEnumerator()
        {
            //每次foreach前想重置光标位置
            Reset();

            //自定义列表类 就继承了IEnumerator 直接返回自己自定义列表类就行
            return this;
        }
        #endregion

        #region IEnumerator里要实现的成员

        //根据当前光标位置 返回元素
        public object Current
        {
            get
            {
                return intArray[position];
            }
        }

        //移动光标到下一位置
        public bool MoveNext()
        {
            //移动光标
            ++position;
            //是否溢出 溢出就不合法
            return position < intArray.Length;
        }

        //reset是重置光标位置 一般写在获取 IEnumerator对象这个函数中
        //用于第一次重置光标位置
        public void Reset()
        {
            position = -1;
        }

        #endregion
    }

    #endregion

    #region 知识点三 用yield return 语法糖实现迭代器

    //yield return 是C#提供给我们的语法糖
    //所谓语法糖,也称糖衣语法
    //主要作用就是将复杂逻辑简单化,可以增加程序的可读性
    //从而减少程序代码出错的机会

    //关键接口:IEnumerable
    //命名空间:using System.Collections;
    //让想要通过foreach遍历的自定义类实现接口中的方法GetEnumerator即可

    //实例
    //自定义列表2 只继承IEnumerable
    class CustomList2 : IEnumerable
    {
        private int[] intArray2;

        public CustomList2()
        {
            intArray2 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        }

        #region IEnumerable里要实现的成员

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < intArray2.Length; i++)
            {
                //yield关键字 配合迭代器使用
                //可以理解为 暂时返回 保留当前的状态
                //一会还会在回来
                //C#的语法糖
                //本质上其实还是有 Current属性 MoveNext方法 Reset方法
                //只是系统检测到yield return关键字 帮我们做好了这部分逻辑
                yield return intArray2[i];
            }
            //这样逐个写也可以
            //yield return intArray[0];
            //yield return intArray[1];
            //yield return intArray[2];
            //yield return intArray[3];
            //yield return intArray[4];
            //yield return intArray[5];
            //yield return intArray[6];
            //yield return intArray[7];
        }

        #endregion
    }

    #endregion

    #region 知识点四 用yield return 语法糖为泛型类实现迭代器

    //泛型自定义列表
    //其实还是继承IEnumerable 其他和非泛型大同小异
    //有泛型的IEnumerable 暂时用不上也可以
    class CustomList<T> : IEnumerable
    {
        private T[] array;

        public CustomList(params T[] array)
        {
            this.array = array;
        }

        #region IEnumerable里要实现的成员
        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < array.Length; i++)
            {
                yield return array[i];
            }
        }
        #endregion
    }
    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("迭代器");

            //主函数内

            #region 知识点二 标准迭代器的实现方法和使用

            //foreach本质 
            //1.先获取 in后面 这个 要遍历的对象的 IEnumerator
            //会调用对象其中的GetEnumerator方法 来获取 要遍历的对象的 IEnumerator
            //所以其实继不继承IEnumerable其实都可以 继承IEnumerable无非就是提醒你实现GetEnumerator方法
            //只要 要遍历的对象 实现了GetEnumerator方法 就可以使用foreach
            //2.执行得到 这个 要遍历的对象的 IEnumerator 中的 MoveNext移动光标方法
            //3.只要MoveNext方法的返回值时true 就会去得到Current 否则MoveNext方法的返回值时false 结束foreach
            //4.然后赋值给item
            //5.重复2-4 直到MoveNext方法的返回值时false 结束foreach

            //创建一个自定义列表实例
            CustomList customList1 = new CustomList();

            //foreach遍历打印customList1中的元素
            foreach (int item in customList1)
            {
                Console.WriteLine("遍历customList1:" + item);
                //遍历customList1:1
                //遍历customList1:2
                //遍历customList1:3
                //遍历customList1:4
                //遍历customList1:5
                //遍历customList1:6
                //遍历customList1:7
                //遍历customList1:8
            }

            //注意:假如没有实现Reset的逻辑 在此foreach遍历打印会打印不出来 因为光标还是在末尾元素 MoveNext为false
            foreach (int item in customList1)
            {
                Console.WriteLine("遍历customList1:" + item);
                //遍历customList1:1
                //遍历customList1:2
                //遍历customList1:3
                //遍历customList1:4
                //遍历customList1:5
                //遍历customList1:6
                //遍历customList1:7
                //遍历customList1:8
            }

            #endregion

            #region 知识点三 用yield return 语法糖实现迭代器

            //创建一个自定义列表2实例
            CustomList customList2 = new CustomList();

            //foreach遍历打印customList2中的元素
            foreach (int item in customList2)
            {
                Console.WriteLine("遍历customList2:" + item);
                //遍历customList2:1
                //遍历customList2:2
                //遍历customList2:3
                //遍历customList2:4
                //遍历customList2:5
                //遍历customList2:6
                //遍历customList2:7
                //遍历customList2:8
            }

            #endregion


            #region 知识点四 用yield return 语法糖为泛型类实现迭代器

            //创建一个自定义泛型列表实例 传入字符串类型
            CustomList<string> customListString = new CustomList<string>("123", "321", "333", "555");

            //foreach遍历打印customListString中的元素
            foreach (string item in customListString)
            {
                Console.WriteLine("customListString:" + item);
                //customListString:123
                //customListString:321
                //customListString:333
                //customListString:555
            }

            #endregion
        }
    }
}

//总结:
//迭代器就是可以让我们在外部直接通过foreach遍历对象中元素而不需要了解其结构
//主要的两种方式
//1.传统方式 继承两个接口 实现里面的方法
//2.用语法糖 yield return 去返回内容 只需要继承一个接口即可

23.3 练习题

请为一个自定义类用两种方法,让其可以被foreach遍历

class语句块外 namespace语句块内

标准迭代器

// 自定义列表 同时继承IEnumerable和IEnumerator
class CustomList : IEnumerable, IEnumerator
{
    // 整形数组
    private int[] intArray;

    // 默认从-1开始的光标 用于表示 数据得到了哪个位置
    private int position = -1;

    // 构造函数初始化整形数组
    public CustomList()
    {
        intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    }

    #region IEnumerable里要实现的成员
    public IEnumerator GetEnumerator()
    {
        // 每次foreach前想重置光标位置
        Reset();

        // 自定义列表类 就继承了IEnumerator 直接返回自己自定义列表类就行
        return this;
    }
    #endregion

    #region IEnumerator里要实现的成员

    // 根据当前光标位置 返回元素
    public object Current
    {
        get
        {
            return intArray[position];
        }
    }

    // 移动光标到下一位置
    public bool MoveNext()
    {
        // 移动光标
        ++position;
        // 是否溢出 溢出就不合法
        return position < intArray.Length;
    }

    // reset是重置光标位置 一般写在获取 IEnumerator对象这个函数中
    // 用于第一次重置光标位置
    public void Reset()
    {
        position = -1;
    }

    #endregion
}

用yield return 语法糖实现迭代器

// 自定义列表2 只继承IEnumerable
class CustomList2 : IEnumerable
{
    private int[] intArray2;

    public CustomList2()
    {
        intArray2 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    }

    #region IEnumerable里要实现的成员

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < intArray2.Length; i++)
        {
            // yield关键字 配合迭代器使用
            // 可以理解为 暂时返回 保留当前的状态
            // 一会还会在回来
            // C#的语法糖
            // 本质上其实还是有 Current属性 MoveNext方法 Reset方法
            // 只是系统检测到yield return关键字 帮我们做好了这部分逻辑
            yield return intArray2[i];
        }
    }

    #endregion
}

主函数内

// 创建一个自定义列表实例
CustomList customList1 = new CustomList();

// foreach遍历打印customList1中的元素
foreach (int item in customList1)
{
    Console.WriteLine("遍历customList1:" + item);
    // 遍历customList1:1
    // 遍历customList1:2
    // 遍历customList1:3
    // 遍历customList1:4
    // 遍历customList1:5
    // 遍历customList1:6
    // 遍历customList1:7
    // 遍历customList1:8
}

// 注意:假如没有实现Reset的逻辑 在此foreach遍历打印会打印不出来 因为光标还是在末尾元素 MoveNext为false
foreach (int item in customList1)
{
    Console.WriteLine("遍历customList1:" + item);
    // 遍历customList1:1
    // 遍历customList1:2
    // 遍历customList1:3
    // 遍历customList1:4
    // 遍历customList1:5
    // 遍历customList1:6
    // 遍历customList1:7
    // 遍历customList1:8
}

// 创建一个自定义列表2实例
CustomList customList2 = new CustomList();

// foreach遍历打印customList2中的元素
foreach (int item in customList2)
{
    Console.WriteLine("遍历customList2:" + item);
    // 遍历customList2:1
    // 遍历customList2:2
    // 遍历customList2:3
    // 遍历customList2:4
    // 遍历customList2:5
    // 遍历customList2:6
    // 遍历customList2:7
    // 遍历customList2:8
}

23.4 练习题代码

using System;
using System.Collections;

namespace Lesson22_练习题
{
    //class语句块外 namespace语句块内

    #region 练习题一
    //请为一个自定义类
    //用两种方法让其可以被foreach遍历

    //标准迭代器
    //自定义列表 同时继承IEnumerable和IEnumerator
    class CustomList : IEnumerable, IEnumerator
    {
        //整形数组
        private int[] intArray;

        //默认从-1开始的光标 用于表示 数据得到了哪个位置
        private int position = -1;

        //构造函数初始化整形数组
        public CustomList()
        {
            intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        }

        #region IEnumerable里要实现的成员
        public IEnumerator GetEnumerator()
        {
            //每次foreach前想重置光标位置
            Reset();

            //自定义列表类 就继承了IEnumerator 直接返回自己自定义列表类就行
            return this;
        }
        #endregion

        #region IEnumerator里要实现的成员

        //根据当前光标位置 返回元素
        public object Current
        {
            get
            {
                return intArray[position];
            }
        }

        //移动光标到下一位置
        public bool MoveNext()
        {
            //移动光标
            ++position;
            //是否溢出 溢出就不合法
            return position < intArray.Length;
        }

        //reset是重置光标位置 一般写在获取 IEnumerator对象这个函数中
        //用于第一次重置光标位置
        public void Reset()
        {
            position = -1;
        }

        #endregion
    }

    //用yield return 语法糖实现迭代器
    //自定义列表2 只继承IEnumerable
    class CustomList2 : IEnumerable
    {
        private int[] intArray2;

        public CustomList2()
        {
            intArray2 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        }

        #region IEnumerable里要实现的成员

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < intArray2.Length; i++)
            {
                //yield关键字 配合迭代器使用
                //可以理解为 暂时返回 保留当前的状态
                //一会还会在回来
                //C#的语法糖
                //本质上其实还是有 Current属性 MoveNext方法 Reset方法
                //只是系统检测到yield return关键字 帮我们做好了这部分逻辑
                yield return intArray2[i];
            }
            //这样逐个写也可以
            //yield return intArray[0];
            //yield return intArray[1];
            //yield return intArray[2];
            //yield return intArray[3];
            //yield return intArray[4];
            //yield return intArray[5];
            //yield return intArray[6];
            //yield return intArray[7];
        }

        #endregion
    }

    #endregion
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("迭代器练习题");

            //主函数内

            #region 练习题一

            //创建一个自定义列表实例
            CustomList customList1 = new CustomList();

            //foreach遍历打印customList1中的元素
            foreach (int item in customList1)
            {
                Console.WriteLine("遍历customList1:" + item);
                //遍历customList1:1
                //遍历customList1:2
                //遍历customList1:3
                //遍历customList1:4
                //遍历customList1:5
                //遍历customList1:6
                //遍历customList1:7
                //遍历customList1:8
            }

            //注意:假如没有实现Reset的逻辑 在此foreach遍历打印会打印不出来 因为光标还是在末尾元素 MoveNext为false
            foreach (int item in customList1)
            {
                Console.WriteLine("遍历customList1:" + item);
                //遍历customList1:1
                //遍历customList1:2
                //遍历customList1:3
                //遍历customList1:4
                //遍历customList1:5
                //遍历customList1:6
                //遍历customList1:7
                //遍历customList1:8
            }

            //创建一个自定义列表2实例
            CustomList customList2 = new CustomList();

            //foreach遍历打印customList2中的元素
            foreach (int item in customList2)
            {
                Console.WriteLine("遍历customList2:" + item);
                //遍历customList2:1
                //遍历customList2:2
                //遍历customList2:3
                //遍历customList2:4
                //遍历customList2:5
                //遍历customList2:6
                //遍历customList2:7
                //遍历customList2:8
            }

            #endregion
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏