C# yield關(guān)鍵字詳解
對(duì)于yield關(guān)鍵字我們首先看一下msdn的解釋?zhuān)?/p>
如果你在語(yǔ)句中使用 yield 關(guān)鍵字,則意味著它在其中出現(xiàn)的方法、運(yùn)算符或 get 訪問(wèn)器是迭代器。 通過(guò)使用 yield 定義迭代器,可在實(shí)現(xiàn)自定義集合類(lèi)型的 IEnumerable 和 IEnumerator 模式時(shí)無(wú)需其他顯式類(lèi)(保留枚舉狀態(tài)的類(lèi),有關(guān)示例,請(qǐng)參閱 IEnumerator<T>)。
yield是一個(gè)語(yǔ)法糖
看msdn 的解釋總是讓人感覺(jué)生硬難懂。其實(shí)yield關(guān)鍵字很好理解。首先我們對(duì)于性質(zhì)有個(gè)了解。yield是一個(gè)語(yǔ)法糖。既然yield是在C#中的一個(gè)語(yǔ)法糖,那么就說(shuō)明yield是對(duì)一種復(fù)雜行為的簡(jiǎn)化,就是將一段代碼簡(jiǎn)化為一種簡(jiǎn)單的形式,方便我們程序員使用。
那么yield到底是對(duì)什么行為的簡(jiǎn)化。我們首先來(lái)看一下yield的使用場(chǎng)景。
還是來(lái)看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的一種使用場(chǎng)景。
我們首先看一下下面的Power方法。該靜態(tài)方法返回一個(gè)IEnumerablel<int>類(lèi)型的參數(shù)。按照我們平常的做法。應(yīng)該對(duì)數(shù)據(jù)執(zhí)行一定操作,然后return一個(gè)IEnumerablel<int>類(lèi)型的參數(shù)。我們把Power方法改造如下:
public static IEnumerable<int> Power(int number, int exponent, string s)
{
int result = 1;
//接口不能實(shí)例化,我們這兒new一個(gè)實(shí)現(xiàn)了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;
}
這是我們平常的思路。但是這樣做就有個(gè)問(wèn)題。這兒要new一個(gè)List,或者任何實(shí)現(xiàn)了IEnumerable接口的類(lèi)型。這樣也太麻煩了吧。要知道IEnumerable是一個(gè)常用的返回類(lèi)型。每次使用都要new一個(gè)LIst,或者其他實(shí)現(xiàn)了該接口的類(lèi)型。與其使用其他類(lèi)型,不如我們自己定制一個(gè)實(shí)現(xiàn)了IEnumerable接口專(zhuān)門(mén)用來(lái)返回IEnumerable類(lèi)型的類(lèi)型。我們自己定制也很麻煩。所以微軟幫我們定制好了。這個(gè)類(lèi)是什么,那就是yield關(guān)鍵字這個(gè)語(yǔ)法糖。
語(yǔ)法糖的實(shí)現(xiàn)(實(shí)現(xiàn)IEnumerable<T>接口的類(lèi))
我們來(lái)看一下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;
}
}
}
}
}
反編譯代碼有三部分,其中程序的入口點(diǎn) private static void Main(string[] args) Power方法 public static IEnumerable<int> Power(int number, int exponent) 和我們自己寫(xiě)的代碼一樣,但是反編譯代碼中還多了一個(gè)密封類(lèi)
private sealed class <Power>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
現(xiàn)在情況已經(jīng)明了了。yield這個(gè)語(yǔ)法糖實(shí)現(xiàn)了一個(gè)實(shí)現(xiàn) IEnumerable<int>接口的類(lèi)來(lái)返回我們需要到 IEnumerable<int>類(lèi)型的數(shù)據(jù)。
我們?cè)倏匆幌路淳幾g后的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__;
}
此時(shí)就確認(rèn),的確是使用了實(shí)現(xiàn)枚舉接口的類(lèi)來(lái)返回我們需要的數(shù)據(jù)類(lèi)型。
每次yield return <expression>;就會(huì)像該類(lèi)的實(shí)例中添加 一條數(shù)據(jù)。當(dāng)yield break;的時(shí)候停止添加。
至此yield的用法就很清楚了。當(dāng)我們需要返回IEnumerable類(lèi)型的時(shí)候,直接yield返回?cái)?shù)據(jù)就可以了。也不用new一個(gè)list,或其他類(lèi)型。所以yield是一個(gè)典型的語(yǔ)法糖。
yield使用中的特殊情況
我們看到編譯器將我們yield的數(shù)據(jù)添加到了一個(gè)集合中。Power方法在編譯器中實(shí)例化了一個(gè)實(shí)現(xiàn)枚舉接口的類(lèi)型。但是我們?cè)赑ower方法中寫(xiě)一些方法,編譯器會(huì)如何處理
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)
{
//這兒調(diào)用了方法。
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("這是一個(gè)異常");
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;
}
}
}
按照我們的理解當(dāng)我們 var test = Power(2, 8, "");的時(shí)候確實(shí)調(diào)用了Power方法。此時(shí)應(yīng)該程序打印Console.WriteLine("Begin to invoke GetItems() method");然后繼續(xù)執(zhí)行 Console.WriteLine("Begin to iterate the collection.");方法。所以打印順序應(yīng)該是
Begin to invoke GetItems() method
Begin to iterate the collection.
但是我們運(yùn)行的時(shí)候卻發(fā)現(xiàn)
打印順序和我們想象的不同。此時(shí)還是去看反編譯代碼。
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__;
}
還是還我們沒(méi)有加打印方法之前一樣。我們的打印方法并沒(méi)有出現(xiàn)在Power方法中,而是被封裝進(jìn)了實(shí)現(xiàn)枚舉接口的類(lèi)方法 private bool MoveNext()中。所以方法不會(huì)立即被執(zhí)行,而是在我們使用數(shù)據(jù)的時(shí)候被執(zhí)行。如果對(duì)此機(jī)制不了解,就容易出現(xiàn)另外一些意想不到的問(wèn)題。例如在Power方法中添加一些驗(yàn)證程序,如果不符合條件就拋出一個(gè)異常。這樣的異常檢查不會(huì)被執(zhí)行。只有我們使用數(shù)據(jù)的時(shí)候才會(huì)執(zhí)行。這樣就失去了檢查數(shù)據(jù)的意義。
另外使用yield還有一些注意事項(xiàng):
你不能在具有以下特點(diǎn)的方法中包含 yield return 或 yield break 語(yǔ)句:
匿名方法。 有關(guān)詳細(xì)信息,請(qǐng)參閱匿名方法(C# 編程指南)。
包含不安全的塊的方法。 有關(guān)詳細(xì)信息,請(qǐng)參閱unsafe(C# 參考)。
異常處理
不能將 yield return 語(yǔ)句置于 try-catch 塊中。 可將 yield return 語(yǔ)句置于 try-finally 語(yǔ)句的 try 塊中。
yield break 語(yǔ)句可以位于 try 塊或 catch 塊,但不能位于 finally 塊。
如果 foreach 主體(在迭代器方法之外)引發(fā)異常,則將執(zhí)行迭代器方法中的 finally 塊。
相關(guān)文章
使用C#判斷一個(gè)字符串是否包含大寫(xiě)字符的五種方法
本文提供了五種判斷字符串是否包含大寫(xiě)字符的方法及其實(shí)現(xiàn)源碼,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-12-12C#強(qiáng)制類(lèi)型轉(zhuǎn)換小結(jié)
任何一門(mén)編程語(yǔ)言均有相關(guān)數(shù)據(jù)類(lèi)型。C#也不例外,不過(guò)轉(zhuǎn)換過(guò)程要注意小類(lèi)型能轉(zhuǎn)換成大類(lèi)型,但大類(lèi)型一般不能轉(zhuǎn)換成小類(lèi)型,下面小編給大家詳解C#強(qiáng)制類(lèi)型轉(zhuǎn)換小結(jié),需要的朋友參考下吧2017-07-07C# 禁止應(yīng)用程序多次啟動(dòng)的實(shí)例
經(jīng)常我們會(huì)有這樣的需求,只讓?xiě)?yīng)用程序運(yùn)行一個(gè)實(shí)體,下面是實(shí)現(xiàn)的方法,有需要的朋友可以參考一下2013-09-09將字符串轉(zhuǎn)換成System.Drawing.Color類(lèi)型的方法
將字符串轉(zhuǎn)換成System.Drawing.Color類(lèi)型的方法,需要的朋友可以參考一下2013-04-04