最近寫代碼為了為了省事兒用了幾個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方法,這就是傳說中的延遲求值吧!
[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方法。
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方法里。
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 fault48 {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 try34 {35 switch (this.<>1__state)36 {37 case 2:38 case 3:39 case 4:40 try41 {42 switch (this.<>1__state)43 {44 case 3:45 case 4:46 try47 {48 }49 finally50 {51 this.<>m__Finally7();52 }53 break;54 }55 }56 finally57 {58 this.<>m__Finally6();59 }60 break;61 }62 }63 finally64 {65 this.<>m__Finally4();66 }67 break;68 69 default:70 return;71 }72 }
新聞熱點
疑難解答