昨天寫了《yield在WCF中的錯誤使用――99%的開發人員都有可能犯的錯誤[上篇]》,引起了一些討論。關于yield關鍵字這個語法糖背后的原理(C#編譯器將它翻譯成什么)其實挺簡單,雖然有時候因為誤用它會導致一些問題,但是它本無過錯。接下來,我們通過這篇短文簡單地談談我所理解的yield。
目錄
一、先看一個簡單的例子
二、了解本質,只需要看看yield最終編譯成什么
三、回到WCF的例子
一、先看一個簡單的例子
我們現在看一個簡單的例子。我們在一個Console應用中編寫了如下一段簡單的程序:返回類型為IEnumerable<string>的方法GetItems以yield return的方式返回一個包含三個字符串的集合,而在方法開始的時候我們打印一段文字表明定義在方法中的操作開始執行。在Main方法中,我們先調用GetItems方法將“集合對象”返回,然后調用其ToArray方法。在調用該方法之前我們打印一段文字表明對集合對象進行迭代。
static IEnumerable<string> GetItems()
{
Console.WriteLine("Begin to invoke GetItems() method");
yield return "Foo";
yield return "Bar";
yield return "Baz";
}
對于上面這段代碼,我想肯定有人會認為得到的結果應該是這樣:
二、了解本質,只需要看看yield最終編譯成什么
上面我們通過“延遲執行”和“可執行表達式”的形式來解釋yield return,僅僅是為了比較好地理解它所體現出來的效果而已,實際上并沒有這回事,這與LINQ的延遲加載更不是一回事。yield return僅僅是C#的一個語法糖而已,是編譯器玩的一個小花招。如何透過這一層“糖紙”看到本質的東西,只需要看看編譯器最終編譯后的與之等效的代碼是什么樣子就可以了。對于上面這個例子來說,不管GetItems方法中以何種方式返回需要的對象,返回值總歸是一個實現了IEnumerable <string>接口的某個類型的對象,我們只需要看看這個類型具有怎樣的定義就知道C#編譯器如果來“解釋”yield return。
我們可以直接利用Reflector打開編譯后的程序集,然后將.NET Framework的版本調成1.0(不支持C#針對后續版本提供的語法糖),這樣就可以以“本質”的方式查看我們編寫的代碼了。如下面的代碼片段所示,GetItems方法中沒有發現我們定義的代碼,而是直接返回一個類型為<GetItems>d__0的對象,看到這里相信讀者朋友們知道為什么執行GetItems方法的時候并沒有文字輸出的真正原因了吧。
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中的錯誤使用――99%的開發人員都有可能犯的錯誤[上篇]》中提到的例子,現在來解釋為什么針對如下兩段代碼,前者拋出的異常不能被WCF正常處理,而后者可以。原因很簡單――兩段代碼拋出異常的時機是不一樣的。對于后者,異常在執行GetItems方法的時候會立即拋出來,WCF會捕獲這個異常并作為應用級別的異常進行正常處理;對于前者,通過上面的分析我們知道異常實際上發生在對返回“集合對象”進行迭代的時候。具體是什么時候呢?其實就是對返回對象進行序列化的時候,此時拋出的異常將將會視為系統異常來處理。
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" };
}
}
新聞熱點
疑難解答