本篇会简单的介绍yield关键字,通过yield关键字返回的类型,以及Reflector反编译的结果来分析yield关键字。最后给出一个仿造的方法。
首先我们看一下yield的用法,他的返回类型返回类型必须是 IEnumerable、IEnumerable<T>、IEnumerator或 IEnumerator<T>,这意味着yield生成的这个对象必须同时实现IEnumerable和IEnumerator这2个接口。
class Program { public static IEnumerable Easy1() { yield return 1; } static void Main(string[] args) { foreach (var item in Program.Easy1()) { Console.WriteLine(item); } } }
上述代码通过Reflector反编译的结果如下:
internal class Program { // Methods public Program(); public static IEnumerable Easy1(); private static void Main(string[] args); // Nested Types [CompilerGenerated] private sealed class <Easy1>d__0 : IEnumerable<object>, IEnumerable, IEnumerator<object>, IEnumerator, IDisposable { // Fields private int <>1__state; private object <>2__current; private int <>l__initialThreadId; // Methods [DebuggerHidden] public <Easy1>d__0(int <>1__state); private bool MoveNext(); [DebuggerHidden] IEnumerator<object> IEnumerable<object>.GetEnumerator(); [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator(); [DebuggerHidden] void IEnumerator.Reset(); void IDisposable.Dispose(); // Properties object IEnumerator<object>.Current { [DebuggerHidden] get; } object IEnumerator.Current { [DebuggerHidden] get; } } }
我们可以看到,这里编译器为我们自动生成了一个叫<Easy1>d_0的类,同时我们可以看到Easy1方法返回的就是该类型:
public static IEnumerable Easy1() { return new <Easy1>d__0(-2); }
<Easy1>d_0经过简化的代码如下,为了能实际使用,我把类名换成了FakeYield:
class FakeYield : IEnumerable, IEnumerator { private int state; private object current; public FakeYield(int state) { this.state = state; } public bool MoveNext() { switch (this.state) { case 0: this.state = -1; this.current = 1; this.state = 1; return true; case 1: this.state = -1; break; } return false; } IEnumerator IEnumerable.GetEnumerator() { if (this.state == -2) { this.state = 0; return this; } return new FakeYield(0); } void IEnumerator.Reset() { throw new NotSupportedException(); } object IEnumerator.Current { get { return this.current; } } }
这里说明一下state不同值的含义,-2表示首次初始化该类,由外部调用构造函数时赋值。0表示初始状态,一切就绪。-1表示一次操作取值完成,1在这个类里是迭代完成的状态。这里我们省略了泛型接口、线程ID initIalThreadId以及IDispose接口。感兴趣的可以自己补完。
如果对迭代器模式有所了解的话,可以看出这里MoveNext方法仅仅是返回了一次true,给current赋值1,也没干啥正经事。不过这正是Easy1方法的忠实体现……
那我们再来看一下稍微复杂一些的yield用法:
class Person { public string Name { get; set; } public int Age { get; set; } public IEnumerable<Person> GetPersons() { yield return new Person { Age = 27, Name = "Leo" }; yield return new Person { Age = 26, Name = "Echo" }; yield return new Person { Age = 25, Name = "Peter" }; } }
和之前比有2点不同,首先GetPersons是一个实例方法,其次返回值是泛型的IEnumerable<Person>。体现在代码中的话,实例方法在该迭代类中会生成一个Person的自引用,在调用该迭代类时传递Person实例对象进去。由于Person对象不在是基本类型,类中产生了一些额外的字段,当然这是因为自动生成的原因,如果人肉去写这个类,自然能够优化。
private sealed class <GetPersons>d__3 : IEnumerable<Person>, IEnumerable, IEnumerator<Person>, IEnumerator, IDisposable { private int <>1__state; private Person <>2__current; public Person <>4__this; public Person <>g__initLocal0; public Person <>g__initLocal1; public Person <>g__initLocal2; private int <>l__initialThreadId; [DebuggerHidden] public <GetPersons>d__3(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.<>g__initLocal0 = new Person(); this.<>g__initLocal0.Age = 0x1b; this.<>g__initLocal0.Name = "Leo"; this.<>2__current = this.<>g__initLocal0; this.<>1__state = 1; return true; case 1: this.<>1__state = -1; this.<>g__initLocal1 = new Person(); this.<>g__initLocal1.Age = 0x1a; this.<>g__initLocal1.Name = "Echo"; this.<>2__current = this.<>g__initLocal1; this.<>1__state = 2; return true; case 2: this.<>1__state = -1; this.<>g__initLocal2 = new Person(); this.<>g__initLocal2.Age = 0x19; this.<>g__initLocal2.Name = "Peter"; this.<>2__current = this.<>g__initLocal2; this.<>1__state = 3; return true; case 3: this.<>1__state = -1; break; } return false; } [DebuggerHidden] IEnumerator<Person> IEnumerable<Person>.GetEnumerator() { if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)) { this.<>1__state = 0; return this; } Person.<GetPersons>d__3 d__ = new Person.<GetPersons>d__3(0); d__.<>4__this = this.<>4__this; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return this.System.Collections.Generic.IEnumerable<YieldTest.Person>.GetEnumerator(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { } Person IEnumerator<Person>.Current { [DebuggerHidden] get { return this.<>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return this.<>2__current; } } }
在这个类中,仍然有一些值得注意的地方。首先是initialThreadId,我对该字段不是很明白,没想清楚该自动生成的类会在多线程中如何使用,还请各位指点迷津。其次是Reset方法,不支持的理由我认为是在自动生成的类中,该方法永远不会被用到。最后还有Dispose方法,在Person类没有引用非托管资源的情况下,Dispose是不需要做任何事情的,但如果引用了需要释放的资源,比如打开了文件,就应该通过该方法来关闭文件。
本文是yield关键字的第一篇,简单的讨论了yield最常见用法的生成类。下篇我们将查看通过for和foreach配合yield返回时生成的类。