当前位置: 首页 > 编程笔记 >

C# yield关键字详解

于正志
2023-03-14
本文向大家介绍C# yield关键字详解,包括了C# yield关键字详解的使用技巧和注意事项,需要的朋友参考一下

对于yield关键字我们首先看一下msdn的解释:

如果你在语句中使用 yield 关键字,则意味着它在其中出现的方法、运算符或 get 访问器是迭代器。 通过使用 yield 定义迭代器,可在实现自定义集合类型的 IEnumerable 和 IEnumerator 模式时无需其他显式类(保留枚举状态的类,有关示例,请参阅 IEnumerator<T>)。

yield是一个语法糖

看msdn 的解释总是让人感觉生硬难懂。其实yield关键字很好理解。首先我们对于性质有个了解。yield是一个语法糖。既然yield是在C#中的一个语法糖,那么就说明yield是对一种复杂行为的简化,就是将一段代码简化为一种简单的形式,方便我们程序员使用。

那么yield到底是对什么行为的简化。我们首先来看一下yield的使用场景。

还是来看msdn上的例子。


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace ConsoleApplication2 {     class Program     {         static void Main(string[] args)         {                      foreach (int i in Power(2, 8, ""))             {                 Console.Write("{0} ", i);             }             Console.ReadKey();         }

        public static IEnumerable<int> Power(int number, int exponent, string s)         {             int result = 1;

            for (int i = 0; i < exponent; i++)             {                 result = result * number;                 yield return result;             }             yield return 3;             yield return 4;             yield return 5;         }

    } }

这是msdn上yield的一种使用场景。

我们首先看一下下面的Power方法。该静态方法返回一个IEnumerablel<int>类型的参数。按照我们平常的做法。应该对数据执行一定操作,然后return一个IEnumerablel<int>类型的参数。我们把Power方法改造如下:


public static IEnumerable<int> Power(int number, int exponent, string s)

        {

            int result = 1;

            //接口不能实例化,我们这儿new一个实现了IEnumerable接口的List

            IEnumerable<int> example = new List<int>();

            for (int i = 0; i < exponent; i++)

            {

                result = result * number;

                (example as List<int>).Add(result);

            }

            return example;

        }

这是我们平常的思路。但是这样做就有个问题。这儿要new一个List,或者任何实现了IEnumerable接口的类型。这样也太麻烦了吧。要知道IEnumerable是一个常用的返回类型。每次使用都要new一个LIst,或者其他实现了该接口的类型。与其使用其他类型,不如我们自己定制一个实现了IEnumerable接口专门用来返回IEnumerable类型的类型。我们自己定制也很麻烦。所以微软帮我们定制好了。这个类是什么,那就是yield关键字这个语法糖。

语法糖的实现(实现IEnumerable<T>接口的类)

我们来看一下yield的反编译代码。


namespace ConsoleApplication2

{

    using System;

    using System.Collections;

    using System.Collections.Generic;

    using System.Diagnostics;

    using System.Runtime.CompilerServices;

    internal class Program     {         private static void Main(string[] args)         {             IEnumerable<int> enumerable = Power(2, 8);             Console.WriteLine("Begin to iterate the collection.");             foreach (int num in Power(2, 8))             {                 Console.Write("{0} ", num);             }             Console.ReadKey();         }

        public static IEnumerable<int> Power(int number, int exponent)         {             <Power>d__0 d__ = new <Power>d__0(-2);             d__.<>3__number = number;             d__.<>3__exponent = exponent;             return d__;         }

        [CompilerGenerated]         private sealed class <Power>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable         {             private int <>1__state;             private int <>2__current;             public int <>3__exponent;             public int <>3__number;             private int <>l__initialThreadId;             public int <result>5__1;             public int exponent;             public int number;

            [DebuggerHidden]             public <Power>d__0(int <>1__state)             {                 this.<>1__state = <>1__state;                 this.<>l__initialThreadId = Environment.CurrentManagedThreadId;             }

            private bool MoveNext()             {                 switch (this.<>1__state)                 {                     case 0:                         this.<>1__state = -1;                         this.<result>5__1 = 1;                         Console.WriteLine("Begin to invoke GetItems() method");                         this.<>2__current = 3;                         this.<>1__state = 1;                         return true;

                    case 1:                         this.<>1__state = -1;                         this.<>2__current = 4;                         this.<>1__state = 2;                         return true;

                    case 2:                         this.<>1__state = -1;                         this.<>2__current = 5;                         this.<>1__state = 3;                         return true;

                    case 3:                         this.<>1__state = -1;                         break;                 }                 return false;             }

            [DebuggerHidden]             IEnumerator<int> IEnumerable<int>.GetEnumerator()             {                 Program.<Power>d__0 d__;                 if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))                 {                     this.<>1__state = 0;                     d__ = this;                 }                 else                 {                     d__ = new Program.<Power>d__0(0);                 }                 d__.number = this.<>3__number;                 d__.exponent = this.<>3__exponent;                 return d__;             }

            [DebuggerHidden]             IEnumerator IEnumerable.GetEnumerator()             {                 return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();             }

            [DebuggerHidden]             void IEnumerator.Reset()             {                 throw new NotSupportedException();             }

            void IDisposable.Dispose()             {             }

            int IEnumerator<int>.Current             {                 [DebuggerHidden]                 get                 {                     return this.<>2__current;                 }             }

            object IEnumerator.Current             {                 [DebuggerHidden]                 get                 {                     return this.<>2__current;                 }             }         }     } }

反编译代码有三部分,其中程序的入口点   private static void Main(string[] args)    Power方法  public static IEnumerable<int> Power(int number, int exponent) 和我们自己写的代码一样,但是反编译代码中还多了一个密封类

  private sealed class <Power>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable

现在情况已经明了了。yield这个语法糖实现了一个实现 IEnumerable<int>接口的类来返回我们需要到 IEnumerable<int>类型的数据。

我们再看一下反编译后的Power方法


public static IEnumerable<int> Power(int number, int exponent)

        {

            <Power>d__0 d__ = new <Power>d__0(-2);

            d__.<>3__number = number;

            d__.<>3__exponent = exponent;

            return d__;

        }

此时就确认,的确是使用了实现枚举接口的类来返回我们需要的数据类型。

每次yield return <expression>;就会像该类的实例中添加 一条数据。当yield break;的时候停止添加。

至此yield的用法就很清楚了。当我们需要返回IEnumerable类型的时候,直接yield返回数据就可以了。也不用new一个list,或其他类型。所以yield是一个典型的语法糖。

yield使用中的特殊情况

我们看到编译器将我们yield的数据添加到了一个集合中。Power方法在编译器中实例化了一个实现枚举接口的类型。但是我们在Power方法中写一些方法,编译器会如何处理


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace ConsoleApplication2

{

    class Program

    {

        static void Main(string[] args)

        {

            //这儿调用了方法。

            var test = Power(2, 8, "");

            Console.WriteLine("Begin to iterate the collection.");

            //Display powers of 2 up to the exponent of 8:

            foreach (int i in Power(2, 8, ""))

            {

                Console.Write("{0} ", i);

            }

            Console.ReadKey();

        }

        public static IEnumerable<int> Power(int number, int exponent, string s)

        {

            int result = 1;

            if (string.IsNullOrEmpty(s))

            {

                //throw new Exception("这是一个异常");

                Console.WriteLine("Begin to invoke GetItems() method");

            }

            for (int i = 0; i < exponent; i++)             {                 result = result * number;                 yield return result;             }             yield return 3;             yield return 4;             yield return 5;         }     } }


按照我们的理解当我们 var test = Power(2, 8, "");的时候确实调用了Power方法。此时应该程序打印Console.WriteLine("Begin to invoke GetItems() method");然后继续执行 Console.WriteLine("Begin to iterate the collection.");方法。所以打印顺序应该是

 Begin to invoke GetItems() method

Begin to iterate the collection.

但是我们运行的时候却发现

打印顺序和我们想象的不同。此时还是去看反编译代码。


namespace ConsoleApplication2

{

    using System;

    using System.Collections;

    using System.Collections.Generic;

    using System.Diagnostics;

    using System.Runtime.CompilerServices;

    internal class Program     {         private static void Main(string[] args)         {             IEnumerable<int> enumerable = Power(2, 8, "");             Console.WriteLine("Begin to iterate the collection.");             foreach (int num in Power(2, 8, ""))             {                 Console.Write("{0} ", num);             }             Console.ReadKey();         }

        public static IEnumerable<int> Power(int number, int exponent, string s)         {             <Power>d__0 d__ = new <Power>d__0(-2);             d__.<>3__number = number;             d__.<>3__exponent = exponent;             d__.<>3__s = s;             return d__;         }

        [CompilerGenerated]         private sealed class <Power>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable         {             private int <>1__state;             private int <>2__current;             public int <>3__exponent;             public int <>3__number;             public string <>3__s;             private int <>l__initialThreadId;             public int <i>5__2;             public int <result>5__1;             public int exponent;             public int number;             public string s;

            [DebuggerHidden]             public <Power>d__0(int <>1__state)             {                 this.<>1__state = <>1__state;                 this.<>l__initialThreadId = Environment.CurrentManagedThreadId;             }

            private bool MoveNext()             {                 switch (this.<>1__state)                 {                     case 0:                         this.<>1__state = -1;                         this.<result>5__1 = 1;                         if (string.IsNullOrEmpty(this.s))                         {                             Console.WriteLine("Begin to invoke GetItems() method");                         }                         this.<i>5__2 = 0;                         while (this.<i>5__2 < this.exponent)                         {                             this.<result>5__1 *= this.number;                             this.<>2__current = this.<result>5__1;                             this.<>1__state = 1;                             return true;                         Label_009D:                             this.<>1__state = -1;                             this.<i>5__2++;                         }                         this.<>2__current = 3;                         this.<>1__state = 2;                         return true;

                    case 1:                         goto Label_009D;

                    case 2:                         this.<>1__state = -1;                         this.<>2__current = 4;                         this.<>1__state = 3;                         return true;

                    case 3:                         this.<>1__state = -1;                         this.<>2__current = 5;                         this.<>1__state = 4;                         return true;

                    case 4:                         this.<>1__state = -1;                         break;                 }                 return false;             }

            [DebuggerHidden]             IEnumerator<int> IEnumerable<int>.GetEnumerator()             {                 Program.<Power>d__0 d__;                 if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))                 {                     this.<>1__state = 0;                     d__ = this;                 }                 else                 {                     d__ = new Program.<Power>d__0(0);                 }                 d__.number = this.<>3__number;                 d__.exponent = this.<>3__exponent;                 d__.s = this.<>3__s;                 return d__;             }

            [DebuggerHidden]             IEnumerator IEnumerable.GetEnumerator()             {                 return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();             }

            [DebuggerHidden]             void IEnumerator.Reset()             {                 throw new NotSupportedException();             }

            void IDisposable.Dispose()             {             }

            int IEnumerator<int>.Current             {                 [DebuggerHidden]                 get                 {                     return this.<>2__current;                 }             }

            object IEnumerator.Current             {                 [DebuggerHidden]                 get                 {                     return this.<>2__current;                 }             }         }     } }

我们看到Power方法


public static IEnumerable<int> Power(int number, int exponent, string s)

        {

            <Power>d__0 d__ = new <Power>d__0(-2);

            d__.<>3__number = number;

            d__.<>3__exponent = exponent;

            d__.<>3__s = s;

            return d__;

        }

还是还我们没有加打印方法之前一样。我们的打印方法并没有出现在Power方法中,而是被封装进了实现枚举接口的类方法  private bool MoveNext()中。所以方法不会立即被执行,而是在我们使用数据的时候被执行。如果对此机制不了解,就容易出现另外一些意想不到的问题。例如在Power方法中添加一些验证程序,如果不符合条件就抛出一个异常。这样的异常检查不会被执行。只有我们使用数据的时候才会执行。这样就失去了检查数据的意义。

具体的例子可以看Artech博主的文章

另外使用yield还有一些注意事项:

你不能在具有以下特点的方法中包含 yield return 或 yield break 语句:

匿名方法。 有关详细信息,请参阅匿名方法(C# 编程指南)。

包含不安全的块的方法。 有关详细信息,请参阅unsafe(C# 参考)。

异常处理

不能将 yield return 语句置于 try-catch 块中。 可将 yield return 语句置于 try-finally 语句的 try 块中。

yield break 语句可以位于 try 块或 catch 块,但不能位于 finally 块。

如果 foreach 主体(在迭代器方法之外)引发异常,则将执行迭代器方法中的 finally 块。

 类似资料:
  • 原文:http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained 注:这是一篇 stackoverflow 上一个火爆帖子的译文 问题 Python 关键字 yield 的作用是什么?用来干什么的? 比如,我正在试图理解下面的代码: def node._get_child_candidates(self,

  • 问题内容: 我知道Java本身没有直接的等效项,但也许是第三方? 真的很方便。当前,我想实现一个迭代器,该迭代器生成树中的所有节点,这大约是带有yield的五行代码。 问题答案: 我知道的两个选项是2007年的Aviad Ben Dov的infomancers- collections库 和2008年的Jim Jimler的YieldAdapter库 (在另一个答案中也提到了)。 两者都允许您使用

  • 本文向大家介绍实例详解C/C++中extern关键字,包括了实例详解C/C++中extern关键字的使用技巧和注意事项,需要的朋友参考一下 1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。 也就是说extern有两个作用,第一个,当它与"C"一起连用时,如: exter

  • 本文向大家介绍彻底理解Python中的yield关键字,包括了彻底理解Python中的yield关键字的使用技巧和注意事项,需要的朋友参考一下 阅读别人的python源码时碰到了这个yield这个关键字,各种搜索终于搞懂了,在此做一下总结: 通常的for...in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。它可以是mylist = [1, 2, 3]

  • 本文向大家介绍详解C# partial 关键字的使用,包括了详解C# partial 关键字的使用的使用技巧和注意事项,需要的朋友参考一下 什么是局部类型? C# 2.0 引入了局部类型的概念。局部类型允许我们将一个类、结构或接口分成几个部分,分别实现在几个不同的.cs文件中。 局部类型适用于以下情况: (1) 类型特别大,不宜放在一个文件中实现。 (2) 一个类型中的一部分代码为自动化工具生成的

  • asm 语法: asm( "instruction" ); asm允许你在你的代码中直接插入汇编语言指令, 各种不同的编译器为这一个指令允许不一致形式, 比如:   asm { instruction-sequence } or asm( instruction ); auto 关键字auto是用来声明完全可选择的局部变量的 bool

  • C# 中的关键字是编译器预先定义好的一些单词,也可以称为保留字或者保留标识符,这些关键字对编译器有特殊的意义,不能用作标识符。但是,如果您非要使用的话也不是没有办法,只需要在关键字前面加上 前缀即可,例如 就是一个有效的标识符,而 则是一个关键字。 在 C# 中,有些关键字在代码的上下文中具有特殊的意义,例如 get 和 set,这样的关键字被称为上下文关键字(contextual keyword

  • 本文向大家介绍c# this关键字用法代码详解,包括了c# this关键字用法代码详解的使用技巧和注意事项,需要的朋友参考一下 用法1 为原始类型扩展方法 先说一下,this 后面跟的类型,就是要拓展方法的类型。注意要写在静态类中的静态方法,不然有些情况下访问 用法2 this代表当前类的实例对象 用法3 用this串联构造函数 用法4 索引器(基于索引器封装EPList,用于优化大数据下频发的L