c# 迭代器实现

1,378 阅读2分钟

开发中如果想要自己实现一个集合数据接口并且可以用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; }
}

控制台正常输出了添加的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也有不少应用的地方例如遍历一个日期 读取文本文件的字符,读取一个内存流