最近写代码为了为了省事儿用了几个yield return,因为我不想New一个List<T>或者T[]对象再往里放元素,就直接返回IEnumerable<T>了。我的代码里还有很多需要Dispose的对象,所以又用了几个using。写着写着我有点心虚了——这样混合使用靠谱吗?
今天我花时间研究一下,并在这里作个笔记,跟大家分享。笔者水平有限,有哪些理解错误或做的不到位的地方,还请各位专家点拨。
这是我写的方法,循环外面一个using,整个方法里代码执行后释放一个对象。循环里面又一个using, 每次循环yield return后要释放一个对象。那是不是任何情况下这些[被创建了的需要释放的]DisposableObject对象最后都会被释放呢?
private static IEnumerable<int> GetNumbers(int count) { using (DisposableObject parentDisposableObject = new DisposableObject("ParentDisposableObject")) { foreach (int number in Enumerable.Range(1, count)) { using (DisposableObject childDisposableObject = new DisposableObject(string.Format("ChildDisposableObject{0}", number))) { //if (number == 4) //{ // throw new Exception("异常。"); //} if (number != 2) { yield return number * 10; } else { Console.WriteLine(" 循环{0} else 代码执行了", number.ToString()); } Console.WriteLine(" 循环{0}else下面的代码执行了", number.ToString()); } } } } }
需要释放资源的类定义如下,创建对象和释放时都有输出。
class DisposableObject : IDisposable { private string _value; public DisposableObject(string value) { _value = value; Console.WriteLine("Create Object {0}", _value); } public void Dispose() { Console.WriteLine("Disposable Object {0}", _value); } }
这里调用下:
static void Main(string[] args) { foreach (int number in GetNumbers(5)) { Console.WriteLine("结果 {0}", number.ToString()); } }
看看运行结果:
我们可以看到:1、循环外面的对象和循环里面的DisposableObject对象都被释放了,这个让我很高兴,要的就是这个效果;2,如果yield return后面还有代码,[yield] return后还会继续执行;3,if-else有作用,不满足条件可以不把该项作为结果返回,不想执行某段代码可以放{}里。这个运行的结果我很满意,就是我想要的!
下面我把抛异常的代码注释去掉,看看循环内抛出的异常后能否正常释放对象。
结果很完美,担忧是多余的,该释放的DisposableObject对象都被释放了!
那么我们简单研究下yield return吧,我写了下面最简单的代码:
private static IEnumerable<int> GetNumbers(int[] numbers) { foreach (int number in numbers) { yield return number*10; } }
把项目编译再反编译成C#2.0,发现代码变成了这个样子:
private static IEnumerable<int> GetNumbers(int[] numbers) { <GetNumbers>d__0 d__ = new <GetNumbers>d__0(-2); d__.<>3__numbers = numbers; return d__; }
这里的<GetNumbers>d__0是个自动生成的类(看来这是高热量的语法糖,吃的是少了,程序集却发胖了!),它实现了IEnumerable<T>,IEnumerator<T>等接口,而上面方法其实就是返回了一个封装了迭代器块代码的计数对象而已,如果您仅仅调用了一下上面这个方法,它可能不会执行循环中的代码,除非触发了返回值的MoveNext方法,这就是传说中的延迟求值吧!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[CompilerGenerated] private sealed class <GetNumbers>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable { // Fields private int <>1__state; private int <>2__current; public int[] <>3__numbers; public int[] <>7__wrap3; public int <>7__wrap4; private int <>l__initialThreadId; public int <number>5__1; public int[] numbers; // Methods [DebuggerHidden] public <GetNumbers>d__0(int <>1__state); private void <>m__Finally2(); private bool MoveNext(); [DebuggerHidden] IEnumerator<int> IEnumerable<int>.GetEnumerator(); [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator(); [DebuggerHidden] void IEnumerator.Reset(); void IDisposable.Dispose(); // Properties int IEnumerator<int>.Current { [DebuggerHidden] get; } object IEnumerator.Current { [DebuggerHidden] get; } } Expand Methods
通过MSIL查看上面的foreach循环会调用MoveNext方法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
entrypoint .maxstack 2 .locals init ( [0] int32 number, [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000) L_0000: ldc.i4.5 L_0001: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> ConsoleApplication1.Program::GetNumbers(int32) L_0006: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() L_000b: stloc.1 L_000c: br.s L_0026 L_000e: ldloc.1 L_000f: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() L_0014: stloc.0 L_0015: ldstr "\u7ed3\u679c\uff1a{0}" L_001a: ldloca.s number L_001c: call instance string [mscorlib]System.Int32::ToString() L_0021: call void [mscorlib]System.Console::WriteLine(string, object) L_0026: ldloc.1 L_0027: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() L_002c: brtrue.s L_000e L_002e: leave.s L_003a L_0030: ldloc.1 L_0031: brfalse.s L_0039 L_0033: ldloc.1 L_0034: callvirt instance void [mscorlib]System.IDisposable::Dispose() L_0039: endfinally L_003a: ret .try L_000c to L_0030 finally handler L_0030 to L_003a
而循环里面的执行内容都在MoveNext方法里。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private bool MoveNext() { try { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<parentDisposableObject>5__1 = new DisposableObject("ParentDisposableObject"); this.<>1__state = 1; this.<>7__wrap5 = Enumerable.Range(1, this.count).GetEnumerator(); this.<>1__state = 2; while (this.<>7__wrap5.MoveNext()) { this.<number>5__2 = this.<>7__wrap5.Current; this.<childDisposableObject>5__3 = new DisposableObject(string.Format("ChildDisposableObject{0}", this.<number>5__2)); this.<>1__state = 3; if (this.<number>5__2 == 4) { throw new Exception("异常。"); } if (this.<number>5__2 == 2) { goto Label_00D0; } this.<>2__current = this.<number>5__2 * 10; this.<>1__state = 4; return true; Label_00C7: this.<>1__state = 3; goto Label_00E8; Label_00D0: Console.WriteLine("循环{0}:else 内代码执行了 ", this.<number>5__2.ToString()); Label_00E8: Console.WriteLine("循环{0}:else下面的代码执行了 ", this.<number>5__2.ToString()); this.<>m__Finally7(); } this.<>m__Finally6(); this.<>m__Finally4(); break; case 4: goto Label_00C7; } return false; } fault { this.System.IDisposable.Dispose(); } }
接着再看下using,也来个最简单的。
using (DisposableObject parentDisposableObject = new DisposableObject("MainDisposableObject")) { Console.WriteLine("执行..."); //throw new Exception("异常。"); }
然后我们看一下对应的MSIL:
.entrypoint .maxstack 1 .locals init ( [0] class ConsoleApplication1.DisposableObject parentDisposableObject) L_0000: ldstr "MainDisposableObject" L_0005: newobj instance void ConsoleApplication1.DisposableObject::.ctor(string) L_000a: stloc.0 L_000b: ldstr "\u6267\u884c..." L_0010: call void [mscorlib]System.Console::WriteLine(string) L_0015: leave.s L_0021 L_0017: ldloc.0 L_0018: brfalse.s L_0020 L_001a: ldloc.0 L_001b: callvirt instance void [mscorlib]System.IDisposable::Dispose() L_0020: endfinally L_0021: ret .try L_000b to L_0017 finally handler L_0017 to L_0021
再换一种C#写法试试:
DisposableObject parentDisposableObject = new DisposableObject("MainDisposableObject"); try { Console.WriteLine("执行..."); //throw new Exception("异常。"); } finally { parentDisposableObject.Dispose(); }
对应的MSIL代码:
.entrypoint .maxstack 1 .locals init ( [0] class ConsoleApplication1.DisposableObject parentDisposableObject) L_0000: ldstr "MainDisposableObject" L_0005: newobj instance void ConsoleApplication1.DisposableObject::.ctor(string) L_000a: stloc.0 L_000b: ldstr "\u6267\u884c..." L_0010: call void [mscorlib]System.Console::WriteLine(string) L_0015: leave.s L_001e L_0017: ldloc.0 L_0018: callvirt instance void ConsoleApplication1.DisposableObject::Dispose() L_001d: endfinally L_001e: ret .try L_000b to L_0017 finally handler L_0017 to L_001e
看看两段MSIL多像啊,特别是最后一句!
最后我们看看yield return 和 using混合使用时,自动生成的<GetNumbers>d__0类是如何保证需要释放资源的DisposableObject对象被释放的,看后我不禁感慨:C#的编译器真是鬼斧神工啊!
1 private bool MoveNext() 2 { 3 try 4 { 5 switch (this.<>1__state) 6 { 7 case 0: 8 this.<>1__state = -1; 9 this.<parentDisposableObject>5__1 = new DisposableObject("ParentDisposableObject"); 10 this.<>1__state = 1; 11 this.<>7__wrap5 = Enumerable.Range(1, this.count).GetEnumerator(); 12 this.<>1__state = 2; 13 while (this.<>7__wrap5.MoveNext()) 14 { 15 this.<number>5__2 = this.<>7__wrap5.Current; 16 this.<childDisposableObject>5__3 = new DisposableObject(string.Format("ChildDisposableObject{0}", this.<number>5__2)); 17 this.<>1__state = 3; 18 if (this.<number>5__2 == 4) 19 { 20 throw new Exception("异常。"); 21 } 22 if (this.<number>5__2 == 2) 23 { 24 goto Label_00D0; 25 } 26 this.<>2__current = this.<number>5__2 * 10; 27 this.<>1__state = 4; 28 return true; 29 Label_00C7: 30 this.<>1__state = 3; 31 goto Label_00E8; 32 Label_00D0: 33 Console.WriteLine("循环{0}:else 内代码执行了 ", this.<number>5__2.ToString()); 34 Label_00E8: 35 Console.WriteLine("循环{0}:else下面的代码执行了 ", this.<number>5__2.ToString()); 36 this.<>m__Finally7(); 37 } 38 this.<>m__Finally6(); 39 this.<>m__Finally4(); 40 break; 41 42 case 4: 43 goto Label_00C7; 44 } 45 return false; 46 } 47 fault 48 { 49 this.System.IDisposable.Dispose(); 50 } 51 } 52 53 54 55
1 public DisposableObject <parentDisposableObject>5__1; 2 3 4 public DisposableObject <childDisposableObject>5__3; 5 6 7 private void <>m__Finally4() 8 { 9 this.<>1__state = -1; 10 if (this.<parentDisposableObject>5__1 != null) 11 { 12 this.<parentDisposableObject>5__1.Dispose(); 13 } 14 } 15 16 private void <>m__Finally7() 17 { 18 this.<>1__state = 2; 19 if (this.<childDisposableObject>5__3 != null) 20 { 21 this.<childDisposableObject>5__3.Dispose(); 22 } 23 } 24 25 void IDisposable.Dispose() 26 { 27 switch (this.<>1__state) 28 { 29 case 1: 30 case 2: 31 case 3: 32 case 4: 33 try 34 { 35 switch (this.<>1__state) 36 { 37 case 2: 38 case 3: 39 case 4: 40 try 41 { 42 switch (this.<>1__state) 43 { 44 case 3: 45 case 4: 46 try 47 { 48 } 49 finally 50 { 51 this.<>m__Finally7(); 52 } 53 break; 54 } 55 } 56 finally 57 { 58 this.<>m__Finally6(); 59 } 60 break; 61 } 62 } 63 finally 64 { 65 this.<>m__Finally4(); 66 } 67 break; 68 69 default: 70 return; 71 } 72 }