C#foreach原理


本文主要記錄我在學習C#中foreach遍歷原理的心得體會。

 

對集合中的要素進行遍歷是所有編碼中經常涉及到的操作,因此大部分編程語言都把此過程寫進了語法中,比如C#中的foreach。經常會看到下面的遍歷代碼:

            var lstStr = new List<string> { "a", "b" };
            foreach (var str in lstStr)
            {
                Console.WriteLine(str);
            }

實際此代碼的執行過程:

復制代碼
            var lstStr = new List<string> {"a", "b"};
            IEnumerator<string> enumeratorLst = lstStr.GetEnumerator();
            while (enumeratorLst.MoveNext())
            {
                Console.WriteLine(enumeratorLst.Current);
            }
復制代碼

會發現有GetEnumerator()方法和IEnumerator<string>類型,這就涉及到可枚舉類型和枚舉器的概念。

為了方便理解,以下為非泛型示例:

復制代碼
    // 摘要:
    //     公開枚舉器,該枚舉器支持在非泛型集合上進行簡單迭代。
    public interface IEnumerable
    {
        // 摘要:
        //     返回一個循環訪問集合的枚舉器。
        //
        // 返回結果:
        //     可用於循環訪問集合的 System.Collections.IEnumerator 對象。
        IEnumerator GetEnumerator();
    }
復制代碼

實現了此接口的類稱為可枚舉類型,是可以用foreach進行遍歷的標志。

方法GetEnumerator()的返回值是枚舉器,可以理解為游標。

復制代碼
    // 摘要:
    //     支持對非泛型集合的簡單迭代。
    public interface IEnumerator
    {
        // 摘要:
        //     獲取集合中的當前元素。
        //
        // 返回結果:
        //     集合中的當前元素。
        //
        // 異常:
        //   System.InvalidOperationException:
        //     枚舉數定位在該集合的第一個元素之前或最后一個元素之后。
        object Current { get; }

        // 摘要:
        //     將枚舉數推進到集合的下一個元素。
        //
        // 返回結果:
        //     如果枚舉數成功地推進到下一個元素,則為 true;如果枚舉數越過集合的結尾,則為 false。
        //
        // 異常:
        //   System.InvalidOperationException:
        //     在創建了枚舉數后集合被修改了。
        bool MoveNext();
        //
        // 摘要:
        //     將枚舉數設置為其初始位置,該位置位於集合中第一個元素之前。
        //
        // 異常:
        //   System.InvalidOperationException:
        //     在創建了枚舉數后集合被修改了。
        void Reset();
    }
復制代碼

而生成一個枚舉器的的工具就是迭代器,以下是自定義一個迭代器的示例(https://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx):

復制代碼
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 (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);

    }
}

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

在有了yield這個關鍵字以后,我們可以通過這樣的方式來創建枚舉器:

復制代碼
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()
    {
        for (int i = 0; i < _people.Length; i++)
        {
            yield return _people[i];
        }
    }

}


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);
    }
}
復制代碼

yield和return一起使用才有意義,這個關鍵字就是為迭代器服務的,所以有yield所在的方法是迭代器,作用是返回相同類型的值的一個序列,執行到yield return 這個語句之后,函數並不直接返回,而是保存此元素到序列中,繼續執行,直到函數體結束或者yield break。

但是生成的此迭代器的函數體並不會在直接調用時運行,只會在foreach中迭代時執行。如:

復制代碼
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

// 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 IEnumerable GetMyEnumerator()
    {
        Console.WriteLine("a");
        for (int i = 0; i < _people.Length; i++)
        {
            Console.WriteLine(i.ToString());
            yield return _people[i];
        }
    }

}


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);
        IEnumerable myEnumerator = peopleList.GetMyEnumerator();
        Console.WriteLine("Start Iterate");
        foreach (Person p in myEnumerator)
        {
            Console.WriteLine(p.firstName + " " + p.lastName);
        }

        Console.ReadLine();
    }
}
復制代碼

結果:

 

  

 

 

 

 

 

 

 

 

 

 

 

 

轉載:https://www.cnblogs.com/alongdada/p/7598119.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM