zoukankan      html  css  js  c++  java
  • 构建可枚举类型(IEnumerable和IEnumerator)

    IEnumerable和IEnumerator

      为了开始对实现既有.NET接口的研究,让我们先看一下IEnumerable和IEnumerator的作用。C#支持关键字foreach,允许我们遍历任何数组类型的内容。虽然看上去只有数组类型才可以使用这个结构,其实任何支持GetEnumerator()方法的类型都可以通过foreach结构进行运算。

      支持这种行为的类或结构实际上是在宣告它们向调用者(如foreach关键字本身)公开了所包含的子项,下面是标准的.NET接口定义:

    namespace System.Collections
    {
        // 这个接口告知调用方对象的子项可以枚举
        public interface IEnumerable
        {
            IEnumerator GetEnumerator();
        }
    }

      可以看到,GetEnumerator()方法返回一个对另一个接口System.Collections.IEnumerator的引用。这个接口提供了基础设施,调用方可以用来移动IEnumerable兼容容器包含的内部对象:

    namespace System.Collections
    {
        // 这个接口允许调用方获取一个容器的子项
        public interface IEnumerator
        {
            object Current { get; } // 获取当前的项(只读属性)
            bool MoveNext();        // 将光标的内部位置向前移动
            void Reset();           // 将光标重置到第一个成员前
        }
    }

       如果想自定义类型支持这些接口,可以手工实现每个方法,这需要花费不少精力。虽然自己开发GetNumerator()、MoveNext()、Current()和Reset()也没问题,但是有一个更简单的方法。因为System.Array类型和其他许多类型已经实现了IEnumerable和IEnumerator接口,你可以简单地将请求委托到System.Array,如下所示:

    namespace CustomEnumerator
    {
        public class Garage : IEnumerable
        {
            // System.Array已经实现了IEnumerator
            private Car[] carArray = new Car[4];
    
            public Garage()
            {
                carArray[0] = new Car("Rusty", 30);
                carArray[1] = new Car("Clunker", 55);
                carArray[2] = new Car("Zippy", 30);
                carArray[3] = new Car("Fred", 30);
            }
    
            public IEnumerator GetEnumerator()
            {
                //  返回数组对象的IEnumerator
                return carArray.GetEnumerator();
            }
        }
    }

       修改Garage类型之后,就可以在C# foreach结构中安全使用该类型了。除此之外,GetNumerator()被定义为公开的,对象用户可以与IEnumerator类型交互:

    namespace CustomEnumerator
    {
        public class Program
        {
            static void Main( string[] args )
            {
                Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****
    ");
                Garage carLot = new Garage();
    
                foreach (Car c in carLot)
                {
                    Console.WriteLine("{0} is going {1} MPH",
                      c.PetName, c.CurrentSpeed);
                }
    
                // 手动与IEnumerator协作
                IEnumerator i = carLot.GetEnumerator();
                i.MoveNext();
                Car myCar = (Car)i.Current;
                Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);
    
                Console.ReadLine();
            }
        }
    }

       如果希望在对象级别隐藏IEnumerable的功能,只需要使用显示接口实现就行了:

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

       这样的话,对象用户就不能找到Garage的GetEnumerator()方法,而foreach结构会在必要的时候在背后获得接口。

    用yield关键字构建迭代器方法

       在以前,如果我们希望构建支持foreach枚举的自定义集合(如Garage),只能实现IEnumerable接口(可能还有IEnumerator接口)。然后,还可以通过迭代器来构建使用foreach循环的类型。

      简单来说,迭代器就是这样一个成员方法,他指定了容器内部项被foreach处理时该如何返回。虽然迭代器方法还必须命名为GetEnumerator(),返回值还是必须为IEnumerator类型,但自定义类型不需要实现原来那些接口了。

      现在,对当前的Garage类型做如下改进:

        public class Garage
        {
            private Car[] carArray = new Car[4];
            ...// 迭代器方法
            public IEnumerator GetEnumerator()
            {
                foreach (Car c in carArray)
                {
                    yield return c;
                }
            }
        }

       注意,这个GetEnumerator()的实现使用内部foreach逻辑迭代每个子项,使用新的yield return语法向调用方返回每个Car对象。yield关键字用来向调用方的foreach结构指定返回值。当到达yield return语句后,当前位置被存储下来,下次调用迭代器时会从这个位置开始执行。

      迭代器方法不一定要通过foreach关键字来返回内容。我们也可以使用如下代码定义迭代器方法:

    public IEnumerator GetEnumerator()
    {
        yield return carArray[0];
        yield return carArray[1];
        yield return carArray[2];
        yield return carArray[3];
    }

      在这个实现中,注意GetEnumerator()方法显示返回新的值给调用者。虽然对于这个示例来说意义不是很大,因为如果我们为carArray成员变量增加更多对象的话,GetEnumerator()方法就不会同步。但是,如果我们希望方法返回能被foreach语法处理的局部数据,这个语法就很有用。

    构建命名迭代器

       还有有趣的一点是,yield关键字从技术上说可以结合任何方法一起使用,无论方法名是什么。这些方法(技术上称为命名迭代器)独特之处在于可以接受许多参数。如果构建命名迭代器的话,需要知道这些方法会返回IEnumerable接口,而不是预计的IEnumerator兼容类型。例如,我们可以为Garage类型增加如下方法:

    public IEnumerable GetTheCars( bool ReturnRevesed )
    {
        // 逆序返回项
        if (ReturnRevesed)
        {
            for (int i = carArray.Length; i != 0; i--)
            {
                yield return carArray[i - 1];
            }
        }
        else
        {
            // 按顺序返回数组中的项
            foreach (Car c in carArray)
            {
                yield return c;
            }
        }
    }

       注意,我们的新方法允许调用者以正序和逆序(如果传入的参数值为true)来获取子项。我们可以按如下所示的代码和新方法进行交互:

    class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine("***** Fun with the Yield Keyword *****
    ");
            Garage carLot = new Garage();
    
            // 使用GetEnumerator()来获取项
            foreach (Car c in carLot)
            {
                Console.WriteLine("{0} is going {1} MPH",
                    c.PetName, c.CurrentSpeed);
            }
    
            Console.WriteLine();
    
            // 使用命名迭代器来获取项(逆序)
            foreach (Car c in carLot.GetTheCars(true))
            {
                Console.WriteLine("{0} is going {1} MPH",
                    c.PetName, c.CurrentSpeed);
            }
            Console.ReadLine();
        }
    }

      命名迭代器是很有用的结构,因为一个自定义容器可以定义多重方式来请求返回的集。

      那么,总结一下可枚举对象的构建吧。记住,如果自定义类型要和C#的foreach关键字一起使用的话,容器就需要定义一个名为GetEnumerator()的方法,它由IEnumerator接口类型来定制。通常,这个方法的实现只是交给保存子对象的内部成员,然而,我们也可以使用yield return语法来提供多个"命名迭代器"方法。

  • 相关阅读:
    基于NFS的PV动态供给(StorageClass)
    Helm学习笔记
    k8s日志收集方案
    okhttputils【 Android 一个改善的okHttp封装库】使用(三)
    PopupWindowMenuUtil【popupwindow样式菜单项列表】
    NewBuiltBottomSheetDialog【新建底部对话框】
    NormalDialogFragmentDemo【普通页面的DialogFragment】
    ArticleRemoveDelDialog【基于AlertDialog的回收删除对话框】
    ConfirmCancelBottomSheetDialog【确认取消底部对话框】
    Android-PickerView【仿iOS的PickerView控件,并封装了时间选择和选项选择这两种选择器】使用
  • 原文地址:https://www.cnblogs.com/gyt-xtt/p/6270160.html
Copyright © 2011-2022 走看看