C# yield在WCF中的錯(cuò)誤使用(二)
昨天寫了《yield在WCF中的錯(cuò)誤使用——99%的開發(fā)人員都有可能犯的錯(cuò)誤[上篇]》,引起了一些討論。關(guān)于yield關(guān)鍵字這個(gè)語(yǔ)法糖背后的原理(C#編譯器將它翻譯成什么)其實(shí)挺簡(jiǎn)單,雖然有時(shí)候因?yàn)檎`用它會(huì)導(dǎo)致一些問(wèn)題,但是它本無(wú)過(guò)錯(cuò)。接下來(lái),我們通過(guò)這篇短文簡(jiǎn)單地談?wù)勎宜斫獾膟ield。
目錄
一、先看一個(gè)簡(jiǎn)單的例子
二、了解本質(zhì),只需要看看yield最終編譯成什么
三、回到WCF的例子
一、先看一個(gè)簡(jiǎn)單的例子
我們現(xiàn)在看一個(gè)簡(jiǎn)單的例子。我們?cè)谝粋€(gè)Console應(yīng)用中編寫了如下一段簡(jiǎn)單的程序:返回類型為IEnumerable<string>的方法GetItems以yield return的方式返回一個(gè)包含三個(gè)字符串的集合,而在方法開始的時(shí)候我們打印一段文字表明定義在方法中的操作開始執(zhí)行。在Main方法中,我們先調(diào)用GetItems方法將“集合對(duì)象”返回,然后調(diào)用其ToArray方法。在調(diào)用該方法之前我們打印一段文字表明對(duì)集合對(duì)象進(jìn)行迭代。
static void Main(string[] args)
{
IEnumerable<string> items = GetItems();
Console.WriteLine("Begin to iterate the collection.");
items.ToArray();
}
static IEnumerable<string> GetItems()
{
Console.WriteLine("Begin to invoke GetItems() method");
yield return "Foo";
yield return "Bar";
yield return "Baz";
}
對(duì)于上面這段代碼,我想肯定有人會(huì)認(rèn)為得到的結(jié)果應(yīng)該是這樣:
Begin to invoke GetItems() method
Begin to iterate the collection.
但是下面才是真正的執(zhí)行結(jié)果。也就是說(shuō),一旦我們?cè)谝粋€(gè)返回類型為IEnumerable或者IEnumerable<T>的方式中通過(guò)yield return返回集合元素,意味著這個(gè)定義在方法中操作會(huì)被“延后執(zhí)行”——操作的真正執(zhí)行不是發(fā)生在方法調(diào)用的時(shí)候,而是延后到對(duì)返回的集合進(jìn)行迭代的時(shí)候。我們大體可以以這樣的方式來(lái)“解釋”這個(gè)現(xiàn)象:一旦我們使用了yield return,返回元素的操作會(huì)被封裝成“可執(zhí)行的表達(dá)式”的方式返回,一旦我們對(duì)集合進(jìn)行迭代的時(shí)候,這些表達(dá)式才會(huì)被執(zhí)行。
Begin to iterate the collection.
Begin to invoke GetItems() method
二、了解本質(zhì),只需要看看yield最終編譯成什么
上面我們通過(guò)“延遲執(zhí)行”和“可執(zhí)行表達(dá)式”的形式來(lái)解釋yield return,僅僅是為了比較好地理解它所體現(xiàn)出來(lái)的效果而已,實(shí)際上并沒(méi)有這回事,這與LINQ的延遲加載更不是一回事。yield return僅僅是C#的一個(gè)語(yǔ)法糖而已,是編譯器玩的一個(gè)小花招。如何透過(guò)這一層“糖紙”看到本質(zhì)的東西,只需要看看編譯器最終編譯后的與之等效的代碼是什么樣子就可以了。對(duì)于上面這個(gè)例子來(lái)說(shuō),不管GetItems方法中以何種方式返回需要的對(duì)象,返回值總歸是一個(gè)實(shí)現(xiàn)了IEnumerable <string>接口的某個(gè)類型的對(duì)象,我們只需要看看這個(gè)類型具有怎樣的定義就知道C#編譯器如果來(lái)“解釋”yield return。
我們可以直接利用Reflector打開編譯后的程序集,然后將.NET Framework的版本調(diào)成1.0(不支持C#針對(duì)后續(xù)版本提供的語(yǔ)法糖),這樣就可以以“本質(zhì)”的方式查看我們編寫的代碼了。如下面的代碼片段所示,GetItems方法中沒(méi)有發(fā)現(xiàn)我們定義的代碼,而是直接返回一個(gè)類型為<GetItems>d__0的對(duì)象,看到這里相信讀者朋友們知道為什么執(zhí)行GetItems方法的時(shí)候并沒(méi)有文字輸出的真正原因了吧。
internal class Program
{
private static IEnumerable<string> GetItems()
{
return new <GetItems>d__0(-2);
}
private sealed class <GetItems>d__0 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable
}
<GetItems>d__0是自動(dòng)生成的類型,它實(shí)現(xiàn)了IEnumerable<string>接口,也實(shí)現(xiàn)了IEnumerator<string>,其 GetEnumerator()方法返回的實(shí)際上就是他自己。至于對(duì)<GetItems>d__0對(duì)象的進(jìn)行迭代的時(shí)候如何返回具體元素,只要看看該類型的定義就一目了然了。如下面的代碼片段所示,集合元素的返回實(shí)現(xiàn)在MoveNext()方法中,方法開始的操作(Console.WriteLine("Begin to invoke GetItems() method"))發(fā)生在第一次迭代的時(shí)候。
private sealed class <GetItems>d__0 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable
{
private int <>1__state;
private string <>2__current;
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
Console.WriteLine("Begin to invoke GetItems() method");
this.<>2__current = "Foo";
this.<>1__state = 1;
return true;
case 1:
this.<>1__state = -1;
this.<>2__current = "Bar";
this.<>1__state = 2;
return true;
case 2:
this.<>1__state = -1;
this.<>2__current = "Baz";
this.<>1__state = 3;
return true;
case 3:
this.<>1__state = -1;
break;
}
return false;
}
string IEnumerator<string>.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
}
三、回到WCF的例子
再次回到《yield在WCF中的錯(cuò)誤使用——99%的開發(fā)人員都有可能犯的錯(cuò)誤[上篇]》中提到的例子,現(xiàn)在來(lái)解釋為什么針對(duì)如下兩段代碼,前者拋出的異常不能被WCF正常處理,而后者可以。原因很簡(jiǎn)單——兩段代碼拋出異常的時(shí)機(jī)是不一樣的。對(duì)于后者,異常在執(zhí)行GetItems方法的時(shí)候會(huì)立即拋出來(lái),WCF會(huì)捕獲這個(gè)異常并作為應(yīng)用級(jí)別的異常進(jìn)行正常處理;對(duì)于前者,通過(guò)上面的分析我們知道異常實(shí)際上發(fā)生在對(duì)返回“集合對(duì)象”進(jìn)行迭代的時(shí)候。具體是什么時(shí)候呢?其實(shí)就是對(duì)返回對(duì)象進(jìn)行序列化的時(shí)候,此時(shí)拋出的異常將將會(huì)視為系統(tǒng)異常來(lái)處理。
public class DemoService : IDemoService
{
public IEnumerable<string> GetItems(string categoty)
{
if (string.IsNullOrEmpty(categoty))
{
throw new FaultException("Invalid category");
}
yield return "Foo";
yield return "Bar";
yield return "Baz";
}
}
public class DemoService : IDemoService
{
public IEnumerable<string> GetItems(string categoty)
{
if (string.IsNullOrEmpty(categoty))
{
throw new FaultException("Invalid category");
}
return new string[] { "Foo", "Bar", "Baz" };
}
}
我個(gè)人覺(jué)得這是WCF值得改進(jìn)的地方,但是目前來(lái)說(shuō)為了避免這樣的問(wèn)題,我推薦將WCF契約接口操作方法中的返回類型定義成數(shù)組,而不是IEnumerable或者IEnumerable<T>(順便說(shuō)一下,WCF針對(duì)Array、List以及其他集合類型的序列化/反序列化行為是一致的),但是我個(gè)人對(duì)IEnumerable或者IEnumerable<T>不排斥。
相關(guān)文章
c#讀取圖像保存到數(shù)據(jù)庫(kù)中(數(shù)據(jù)庫(kù)保存圖片)
這篇文章主要介紹了使用c#讀取圖像保存到數(shù)據(jù)庫(kù)中的方法,大家參考使用吧2014-01-01
使用Deflate算法對(duì)文件進(jìn)行壓縮與解壓縮的方法詳解
本篇文章是對(duì)使用Deflate算法對(duì)文件進(jìn)行壓縮和解壓縮的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
C#實(shí)現(xiàn)上位機(jī)與歐姆龍PLC通訊(FINS)
這篇文章主要介紹了C#實(shí)現(xiàn)上位機(jī)與歐姆龍PLC通訊(FINS)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
C#實(shí)現(xiàn)人民幣大寫轉(zhuǎn)換示例代碼
這篇文章主要介紹了C#實(shí)現(xiàn)人民幣大寫轉(zhuǎn)換,需要的朋友可以參考使用2013-12-12

