昨天写了《yield在WCF中的错误使用——99%的开发人员都有可能犯的错误[上篇]》,引起了一些讨论。关于yield关键字这个语法糖背后的原理(C#编译器将它翻译成什么)其实挺简单,虽然有时候因为误用它会导致一些问题,但是它本无过错。接下来,我们通过这篇短文简单地谈谈我所理解的yield。
目录
一、先看一个简单的例子
二、了解本质,只需要看看yield最终编译成什么
三、回到WCF的例子
一、先看一个简单的例子
我们现在看一个简单的例子。我们在一个Console应用中编写了如下一段简单的程序:返回类型为IEnumerable<string>的方法GetItems以yield return的方式返回一个包含三个字符串的集合,而在方法开始的时候我们打印一段文字表明定义在方法中的操作开始执行。在Main方法中,我们先调用GetItems方法将“集合对象”返回,然后调用其ToArray方法。在调用该方法之前我们打印一段文字表明对集合对象进行迭代。
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";
}
</div>
对于上面这段代码,我想肯定有人会认为得到的结果应该是这样:
Begin to invoke GetItems() method
Begin to iterate the collection.
</div>
但是下面才是真正的执行结果。也就是说,一旦我们在一个返回类型为IEnumerable或者IEnumerable<T>的方式中通过yield return返回集合元素,意味着这个定义在方法中操作会被“延后执行”——操作的真正执行不是发生在方法调用的时候,而是延后到对返回的集合进行迭代的时候。我们大体可以以这样的方式来“解释”这个现象:一旦我们使用了yield return,返回元素的操作会被封装成“可执行的表达式”的方式返回,一旦我们对集合进行迭代的时候,这些表达式才会被执行。
Begin to iterate the collection.
Begin to invoke GetItems() method
</div>
二、了解本质,只需要看看yield最终编译成什么
上面我们通过“延迟执行”和“可执行表达式”的形式来解释yield return,仅仅是为了比较好地理解它所体现出来的效果而已,实际上并没有这回事,这与LINQ的延迟加载更不是一回事。yield return仅仅是C#的一个语法糖而已,是编译器玩的一个小花招。如何透过这一层“糖纸”看到本质的东西,只需要看看编译器最终编译后的与之等效的代码是什么样子就可以了。对于上面这个例子来说,不管GetItems方法中以何种方式返回需要的对象,返回值总归是一个实现了IEnumerable <string>接口的某个类型的对象,我们只需要看看这个类型具有怎样的定义就知道C#编译器如果来“解释”yield return。
我们可以直接利用Reflector打开编译后的程序集,然后将.NET Framework的版本调成1.0(不支持C#针对后续版本提供的语法糖),这样就可以以“本质”的方式查看我们编写的代码了。如下面的代码片段所示,GetItems方法中没有发现我们定义的代码,而是直接返回一个类型为<GetItems>d__0的对象,看到这里相信读者朋友们知道为什么执行GetItems方法的时候并没有文字输出的真正原因了吧。
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
}
</div>
<GetItems>d__0是自动生成的类型,它实现了IEnumerable<string>接口,也实现了IEnumerator<string>,其 GetEnumerator()方法返回的实际上就是他自己。至于对<GetItems>d__0对象的进行迭代的时候如何返回具体元素,只要看看该类型的定义就一目了然了。如下面的代码片段所示,集合元素的返回实现在MoveNext()方法中,方法开始的操作(Console.WriteLine("Begin to invoke GetItems() method"))发生在第一次迭代的时候。
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;
}<