C# IEnumerable接口

C# 中如果需要使用关键字foreach去迭代(轮询)一遍一个集合的内容,那么就需要这个类实现IEnumerable接口。

C# 很多官方提供的Collection集合已经实现了IEnumerable接口的,比如ArrayList,Queue,Stack等类都实现了IEnumerable接口,我们可以放心使用foreach。(ArrayList是通过IList接口间接包含了IEnumerable接口,Queue和Stack则是通过拥有ICollection接口间接拥有IEnumerable接口)。

public interface IList : System.Collections.ICollection
public interface ICollection : System.Collections.IEnumerable

如果要自己实现一个拥有迭代器(也就是foreach功能)的类,那就可以包含IEnumerable接口。

IEnumerable接口仅仅需要实现一个GetEnumerator()方法:

public System.Collections.IEnumerator GetEnumerator ();

观察这方法,需要返回一个叫做IEnumerator的接口,因此,一个类要想可迭代,还需要进一步实现IEnumerator类,这个才是真正获取到的迭代器,本文我们暂且称这个类为辅助类。

IEnumerator类要实现的方法和属性会多一些,包括以下内容:

public object Current { get; }
public bool MoveNext ();
public void Reset ();

至此,要想实现一个拥有迭代器的类,我们可以(注意,是可以,不是必须)实现一个IEnumerable接口,这时还需要实现一个拥有IEnumerator接口的辅助类。

举个例子(这个例子来自微软):

using System;
using System.Collections;

// Simple business object.
public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }

    public string firstName;
    public string lastName;
}

// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable
{
    private Person[] _people;
    public People(Person[] pArray)
    {
        _people = new Person[pArray.Length];

        for (int i = 0; i < pArray.Length; i++)
        {
            _people[i] = pArray[i];
        }
    }

// Implementation for the GetEnumerator method.
    IEnumerator IEnumerable.GetEnumerator()
    {
       return (IEnumerator) GetEnumerator();
    }

    public PeopleEnum GetEnumerator()
    {
        return new PeopleEnum(_people);
    }
}

// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }

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

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public Person Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

class App
{
    static void Main()
    {
        Person[] peopleArray = new Person[3]
        {
            new Person("John", "Smith"),
            new Person("Jim", "Johnson"),
            new Person("Sue", "Rabon"),
        };

        People peopleList = new People(peopleArray);
        // 下面的foreach这里就是用到了迭代器。
        foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);
    }
}

/* This code produces output similar to the following:
 *
 * John Smith
 * Jim Johnson
 * Sue Rabon
 *
 */

这个例子中,我们看到实现了一个People类,main方法中可以对peopleList实施了foreach迭代。而之所以可以对peopleList进行迭代,就是因为People类实现了IEnumerable接口。因为需要实现IEnumerable接口,因此,也就实现了一个IEnumerator接口。

代码里,我们发现它有两个Current,这个涉及到隐式接口实现和显示接口实现,请参考文章C# 隐式实现接口和显示实现接口

仔细观察这个PeopleEnum类,里面有个Person数组。position相当于一个游标(也就是数组的下标),游标为-1的时候表示在数组第一项之前,0表示数组的第一个项,以此类推。Current属性表示当前迭代器所指向的这个项,当越界时返回一个异常。MoveNext()方法就是需要向前移动游标,并且当游标越界时需要返回false,否则返回true。Reset()就是把游标初始化为-1。当我们自己想实现一个类似的IEnumerator迭代器,那么就可以完全类似拷贝这个例子去实现自己的迭代器。

这里需要注意的是,如果一个类可以foreach,并不是必须实现IEnumerable接口,事实上,只要这个类有一个GetEnumerator()方法即可。由于GetEnumerator()方法需要有一个返回类,这个返回类需要拥有IEnumerator接口,因此还需要实现IEnumerator接口。事实上,这个返回类只需要实现Current, MoveNext()和Reset()方法即可。

下面更改下上面的例子(去掉IEnumerable和IEnumerator两个接口名字):

using System;
using System.Collections;

// Simple business object.
public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }

    public string firstName;
    public string lastName;
}

// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People 
{
    private Person[] _people;
    public People(Person[] pArray)
    {
        _people = new Person[pArray.Length];

        for (int i = 0; i < pArray.Length; i++)
        {
            _people[i] = pArray[i];
        }
    }

// Implementation for the GetEnumerator method.
 

    public PeopleEnum GetEnumerator()
    {
        return new PeopleEnum(_people);
    }
}

// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum  
{
    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }

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

 
    public Person Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

class App
{
    static void Main()
    {
        Person[] peopleArray = new Person[3]
        {
            new Person("John", "Smith"),
            new Person("Jim", "Johnson"),
            new Person("Sue", "Rabon"),
        };

        People peopleList = new People(peopleArray);
        foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);
    }
}

/* This code produces output similar to the following:
 *
 * John Smith
 * Jim Johnson
 * Sue Rabon
 *
 */

这个例子仍然是可以正常运行的。

这说明一个类要想可以foreach迭代,不需要显示实现IEnumerable接口和IEnumerator接口,只需要实现GetEnumerator()方法,实现一个GetEnumerator()方法返回辅助类,这个辅助类只需要实现Current, MoveNext()和Reset()方法即可。

细心地同学肯定会发现,代码中根本没有直接调用GetEnumerator(),Current, MoveNext()和Reset()啊。实际上这是被foreach隐藏掉了。VS Code里,如果查看MoveNext()方法时,会发现它被引用了一次,如下图所示:

同样的,GetEnumerator(),Current也被foreach语句引用了。这是foreach底层机制调用的,所以我们看不到。

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值