昨天写了《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" };
}
}
新闻热点
疑难解答