c# 迭代器實現

就是我發表於2018-12-09

開發中如果想要自己實現一個集合資料介面並且可以用foreach來遍歷該集合,一般需要實現2個類

IEnumerable

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}
複製程式碼

IEnumerator

public interface IEnumerator
{
    bool MoveNext();
    void Reset();
    object Current { get; }
}
複製程式碼

GetEnumerator()方法作用是需要得到一個IEnumerator介面的例項

MoveNext() 方法作用是判斷這次是否可以遍歷

Reset() 重置該迭代器的索引

Current 取出當前遍歷的資料

接下來編寫2個簡單的類來實現foreach的遍歷

public class MyList<TEntity> : IEnumerable<TEntity>
{
    private readonly int _initLength;
    private TEntity[] _list;
    
    public MyList()
    {
        _list = new TEntity[10];
        _initLength = 10;
    }
    
    public IEnumerator<TEntity> GetEnumerator()
    {
        return new MyEnumeratir(_list);
    }
    
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
    
    public void Add(TEntity entity)
    {
        if(entity==null)
            throw new NullReferenceException("新增的entity 物件為Null");
        if (getNullLocation() == -1) ResetSize();
        int index = getNullLocation();
        _list[index] = entity;
    }
    
    private int getNullLocation()
    {
        for (int i = 0; i < _list.Length; i++)
        {
            if (_list[i] == null)
            {
                return i;
            }
        }
        return -1;
    }
    
    private void ResetSize()
    {
        TEntity[] newList = new TEntity[_list.Length+10];
        for (int i = 0; i < _list.Length; i++)
        {
             newList[i] = _list[i];
        }
        _list = newList;
    }
}

public class MyEnumerator<TEntity>:IEnumerator<TEntity>
{
    private TEntity[] _list;
    private int _index;
    private TEntity _current;
    
    public MyEnumerator(TEntity[] list)
    {
        _index = 1;
        _list = list;
    }
    
    public bool MoveNext()
    {
        _index++;
        if(_index==_list.Length) return false;
        TEntity obj = _list[_index];
        if(obj==null) return false;
        return true;
    }
    
    public void Reset()
    {
        _index= -1;
    }
    
    TEntity IEnumerator<TEntity>.Current
    {
        get
        {
            _current = _list[_index];
            return _current;
        }
    }
    
    public Object Current => _current;
}
複製程式碼

上面的編寫的程式碼簡單的實現了集合並且可以隨意新增資料,接下來新增一下資料驗證這個集合類是否可以正常遍歷

class Program
{
    public static void Main(string[] args)
    {
        MyList<Student> myList = new MyList<Student>();
    }
    myList.Add(new Student
    {
        Name = "周杰倫",
        Age = 18,
        Sex = "男"
    });
    myList.Add(new Student
    {
        Name = "張靚穎",
        Age = 20,
        Sex = "女"
    });
    myList.Add(new Student
    {
        Name = "雅典啊",
        Age = 1000,
        Sex = "女"
    });
    foreach (var student in myList)
    {
        Console.WriteLine($"姓名:{student.Name} ----- 年齡:{student.Age} ----- 性別:{student.Sex}");
    }
    Console.ReadLine();
}
public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Sex { get; set; }
}
複製程式碼

c# 迭代器實現

控制檯正常輸出了新增的3條Student的例項物件

上面的程式碼中MyEnumerator 編寫了很長的程式碼還需要判斷邊條條件,如果要投入到實際生產環境當中還需要編寫更多的邊界條件判斷很是麻煩。 不過C#開發團隊提供了一個更加簡單的語法糖來實現以上的操作那就是 關鍵字 yield

現在改寫一下MyList中的方法GetEnumerator()

public IEnumerator<TEntity> GetEnumerator()
{
    //return new MyEnumeratir(_list);
    for(int i=0;i<=_list.Length-1;i++)
    {
        if(_list[i]!=null)
        {
            yield return _list[i];
        }
    }
}
複製程式碼

這樣看起來程式碼乾淨了不少哈 不過yield使用起來有幾條限制

1.不能將 yield return 語句置於 try-catch 塊中。 可將 yield return 語句置於 try-finally 語句的 try 塊中。
2.可將 yield break 語句置於 try 塊或 catch 塊中,但不能將其置於 finally 塊中。
3.如果 foreach 主體(在迭代器方法之外)引發異常,則將執行迭代器方法中的 finally 塊。

上面的例項是解釋了迭代器如何實現,實際開發中你這樣寫程式碼得讓別人給罵死。
實際場景中yield也有不少應用的地方例如遍歷一個日期 讀取文字檔案的字元,讀取一個記憶體流

相關文章