C#中关键字 yield 的使用

C#中关键字 yield 的使用

1.背景知识点

(1)迭代器

也叫枚举器,是实现了接口 IEnumerator 的实例。它提供了一种方式以获取集合的下一个元素,进而允许“实现它的类或结构”可以遍历集合,并返回集合的元素。

接口 IEnumerable ,用于对外公开一个枚举器。

//枚举器
public interface IEnumerator
{
    //获取枚举器当前位置的元素
    object Current { get; }

    //将枚举器前进到集合的下一个元素:
    //如果枚举器已成功推进到下一个元素,返回 true;
    //如果枚举器已超过集合的末尾,返回 false
    bool MoveNext();

    //将枚举器设置为其初始位置
    void Reset();
}

//对外公开一个枚举器
public interface IEnumerable
{
    //获取枚举器
    IEnumerator GetEnumerator();
}

//支持泛型的枚举器
public interface IEnumerator<out T> : IEnumerator, IDisposable
{
    //获取枚举器当前位置的元素
    T Current { get; }
}

//对外公开一个支持泛型的枚举器
public interface IEnumerable<out T> : IEnumerable
{
    //获取枚举器
    IEnumerator<T> GetEnumerator();
}

public interface IDisposable
{
    //释放或重置非托管资源
    void Dispose();
}
View Code

示例代码,用于实现一个迭代器 Test:包含一个字符串数组、一个索引,实现了属性 Current,以及实现了方法 MoveNext()、Reset()、Dispose()、GetEnumerator() 等。

public partial class Test : IEnumerable<string>, IEnumerator<string>
{
    public Test() { Reset(); }

    private readonly string[] array = { " a", " b", " c" };

    private int index;

    public string Current
    {
        get
        {
            return array[index];
        }
    }

    object IEnumerator.Current { get { return array[index]; } }

    public bool MoveNext()
    {
        return ++index < array.Length;
    }

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

    public void Dispose() { }

    public IEnumerator<string> GetEnumerator()
    {
        return this;
    }

    IEnumerator IEnumerable.GetEnumerator() { return this; }
}
View Code

C# 语言已经内建了对迭代器的支持,相关的关键字有 foreach,yield 等。.NET 类库也提供了许多有用的迭代器,例如:List、Collection、Dictionary 等。

(2)foreach 

使用 foreach 关键字可以遍历集合。在 foreach 语句,被遍历的对象必须实现 GetEnumerator() 方法,并且 public 以及返回类型 IEnumerator 或 IEnumerator<T>,否则将报错。

internal partial class Program
{
    static void Main(string[] args)
    {
        Task.Run(() =>
        {
            ToTest2();
        });

        Console.ReadKey();
    }

    private static void ToTest2()
    {
        Test test = new Test();

        //foreach
        foreach (var item in test)
        {
            Console.WriteLine(item);
        }
    }
}
View Code

运行结果 :

它等效于以下代码:

private static void ToTest3()
{
    Test test = new Test();
    while (test.MoveNext())
    {
        Console.WriteLine(test.Current);
    }
}
View Code

2.关键字 yield

(1)用法 

   1)yield 必须结合 return 或者 break 一起使用:

  • yield return [value]:在迭代中提供下一个值;
  • yield break:显式表示迭代结束。

   2)包含 yield return 的方法,它的返回类型必须是

  • IEnumerable 或 IEnumerable<T>
  • IEnumerator 或 IEnumerator<T>
  • IAsyncEnumerable<T>
  • IAsyncEnumerator<T>

  关于接口 IAsyncEnumerable<T>、IAsyncEnumerator<T> 请参考:《C#中 IAsyncEnumerable 与 IAsyncEnumerator 的使用

(2)原理

包含 yield return 的方法,会被编译器视为一个迭代器的实现。

这个迭代器的特性:

  • 每执行到 yield return,视为迭代器 MoveNext() 返回 true;
    每执行到 yield break,视为迭代器 MoveNext() 返回 false;
    方法自动运行结束时,视为迭代器 MoveNext() 返回 false;
  • 将 yield return 后的  [value],组成一个“枚举”,
  • 按语句: yield return [value];  的执行顺序,依次返回  [value]。

对一个迭代器的实现,可以简化为以下实现:

开发者不用再创建元素集合、索引,不再实现属性 Current,以及不用实现方法 MoveNext()、Reset()、Dispose() 等,这些工作都交给了编译器,由编译器自动完成。

public class Test2 : IEnumerable<string>
{
    public IEnumerator<string> Y()
    {
        yield return " a";
        yield return " b";
        yield return " c";
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this.Y();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

internal partial class Program
{
    static void Main(string[] args)
    {
        Task.Run(() =>
        {
            ToTest3();
        });

        Console.ReadKey();
    }


    private static void ToTest3()
    {
        Test2 test2 = new Test2();

        //foreach
        foreach (var item in test2)
        {
            Console.WriteLine(item);
        }
    }
}
View Code

甚至进一步简化为以下实现,连方法 GetEnumerator()  都不用实现,一并交给了编译器:

public class Test2
{
    public IEnumerable<string> Y()
    {
        yield return " a";
        yield return " b";
        yield return " c";
    }
}

internal partial class Program
{
    static void Main(string[] args)
    {
        Task.Run(() =>
        {
            ToTest3();
        });

        Console.ReadKey();
    }


    private static void ToTest3()
    {
        Test2 test2 = new Test2();

        //foreach
        foreach (var item in test2.Y())
        {
            Console.WriteLine(item);
        }
    }
}
View Code

对简化实现的迭代器,仍然可以使用原始的方式进行调用:

private static void ToTest3()
{
    Test2 test2 = new Test2();
    var enumerator = test2.Y().GetEnumerator();

    while (enumerator.MoveNext())
    {
        Console.WriteLine(enumerator.Current);
    }
}
View Code

(3)总结

实现一个“包含 yield return 的方法”,即是实现一个迭代器。

3.执行顺序

  • “yield return 语句”的执行顺序 ,即是迭代器返回元素的顺序;
  • “yield return 语句”并不是“return 语句”,因此,在它之后可以有其他语句
  • 每执行到“yield return 语句”时(相当于迭代器 MoveNext() 返回 true 时),则返回一个元素,程序将执行一次“ foreach 的循环体”;
    执行一次“foreach 的循环体”之后,程序将从上一个“yield return 语句”开始,继续执行方法之后的代码
  • 使用 “yield break 语句”,显式退出迭代,否则将等到方法结束时,自动结束迭代;
    在“yield break 语句”之后的代码不被执行【“yield break 语句”才是类似“return 语句”的存在】;
    显式退出迭代或自动结束迭代,相当于迭代器 MoveNext() 返回 false,不再执行“foreach 的循环体”。

 

posted @ 2014-11-05 16:05  误会馋  阅读(395)  评论(0编辑  收藏  举报