程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C# 基礎小知識之yield 關鍵字,

C# 基礎小知識之yield 關鍵字,

編輯:C#入門知識

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; } } } } } View Code

反編譯代碼有三部分,其中程序的入口點   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; } } } } } View Code

我們看到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://www.cnblogs.com/santian/p/4389675.html

博客地址:一天兩天三天

轉載請以超鏈接形式標明文章原始出處。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved