欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

迭代器的实现

程序员文章站 2022-05-03 18:04:42
...

迭代器的实现

迭代器模式是行为模式的一种范例,行为模式是一种简化对象之间通信的一种设计模式。在.NET中,迭代器模式是通过IEnumerator和IEnumerable接口以及它们的泛型版本来实现的。如果某个类实现了IEnumerable接口,就说明它可以被迭代访问,调用GetEnumerator()方法将返回IEnumerator的实现,这个就是迭代器本身。

在C# 1.0中,利用foreach语句实现了访问迭代器的内置支持,让集合的遍历变得简单、明了。其实,foreach的实现就是调用GetEnumerator和MoveNext方法以及Current属性。所以说,在C# 1.0中要获得迭代器就必须实现IEnumerable接口中的GetEnumerator方法,要实现一个迭代器就要实现IEnumerator接口中的MoveNext和Reset方法。

//定义一个类,获得迭代器,实现IEnumerable接口
public class MyIEnumerable : IEnumerable
{
    private string[] strList;
    public MyIEnumerable(string[] strList)
    {
        this.strList = strList;
    }

    public IEnumerator GetEnumerator()
    {
        //C#1.0返回一个实现的迭代器
        return new MyIEnumerator(strList);
    }
}
//实现迭代器,实现IEnumerator接口中的MoveNext和Reset方法
public class MyIEnumerator : IEnumerator
{
    private string[] strList;
    private int position;

    public MyIEnumerator(string[] strList)
    {
        this.strList = strList;
        position = -1;
    }
    public object Current
    {
        get
        {
            return strList[position];
        }
    }

    public bool MoveNext()
    {
        position++;
        if (position < strList.Length)
            return true;
        return false;
    }

    public void Reset()
    {
        position = -1;
    }
}

上面这段代码,要40行代码,在C# 2.0中提供的语法糖来简化迭代器的实现,可以通过yield关键字来简化迭代器的实现。下列类中的GetEnumerator()方法它不是一个普通的方法,这个yield return告诉c#编译器,这是实现一个迭代器的方法。这个方法被声明为返回一个IEnumerator接口,所以就只能使用迭代器块来实现返回类型为IEnumerable、IEnumerator或泛型等价物的方法。在迭代器块中不允许包含普通的return语句——只能是yield return。

//定义一个类,获得迭代器,实现IEnumerable接口
public class MyIEnumerable : IEnumerable
{
    private string[] strList;
    public MyIEnumerable(string[] strList)
    {
        this.strList = strList;
    }

    public IEnumerator GetEnumerator()
    {
        //使用c#2.0中yield关键字来实现,不需要定义MyIEnumerator类就能实现
        for(int i = 0; i<strList.Length;i++)
        {
            yield return strList[i];
        }
    }
}

也可以创建一个类来实现这个迭代器。使用“c#嵌套类型可以访问它外层类型的私有类型”这一特点,就是说,我们仅需要存储一个指向“父级”MyIEnumerable类型的引用和关于所访问到的位置的状态,如下所示。注意,我们能看到MyIEnumerator这个类是MyIEnumerable的子类。

public class MyIEnumerable : IEnumerable
{
    private object[] values;
    private int startPosition;
    public MyIEnumerable(object[] values, int startPosition=0)
    {
        this.values = values;
        this.startPosition = startPosition;
    }

    public IEnumerator GetEnumerator()
    {
        //return new MyIEnumerator(this);
        for(int i=0;i<values.Length;i++)
        {
            yield return values[(i + startPosition) % values.Length];
        }
    }

    public class MyIEnumerator : IEnumerator
    {
        private MyIEnumerable parent;
        private int position;

        internal MyIEnumerator(MyIEnumerable parent)
        {
            this.parent = parent;
            position = -1;
        }
        public object Current
        {
            get
            {
                if (position == -1)
                    throw (new System.InvalidOperationException("枚举尚未开始,请先调用MoveNext"));
                return parent.values[(position + parent.startPosition) % parent.values.Length];
            }
        }

        public bool MoveNext()
        {
            if (position != parent.values.Length)
                position++;
            return position < parent.values.Length;
        }

        public void Reset()
        {
            position = -1;
        }
    }
}

接下来我们创建一个控制台程序来运行它,这里我用了迭代的方式,这两种运行结果是一样的,可以看出,这个foreach语句中实际就是运行了这个迭代器,先使用GetEnumerator方法之后实现迭代器,然后使用MoveNext语句。不过这两种还是有一个不同的,foreach会在最后调用Dispose方法,而且这很重要。还有一个小知识,foreach语句中的那个集合实际上实现了GetEnumerator方法的都可以运行,而不是想象中的必须实现IEnumerable接口。

static void Main(string[] args)
{
    string[] strList = new string[] { "a", "b", "c" };
    MyIEnumerable mi = new MyIEnumerable(strList);
    foreach(var item in mi)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("==========================================================");
    var list = mi.GetEnumerator();
    while(list.MoveNext())
    {
        Console.WriteLine(list.Current);
    }
    Console.ReadKey();
}

还有如果你运行下列代码你会发现有趣的东西,运行结果为“b,c,a”。这是我看《深入了解c#》看到的,就是,这个自己实现的迭代器,可以设置它的逻辑“起点”。所以可以从1开始,就能看到元素1,2和0依次返回。

static void Main(string[] args)
{
    string[] strList = new string[] { "a", "b", "c" };
    MyIEnumerable mi = new MyIEnumerable(strList,1);
    foreach(var item in mi)
    {
        Console.WriteLine(item);
    }
    Console.ReadKey();
}

本文主要借鉴了《深入理解c#》,非常感谢。