1.例項解析yiled的作用
最近參加java筆試題第一次見到yield這個關鍵字,既然遇見了那肯定要掌握,下面是C#中關於yield關鍵字的總結。yield這個關鍵字作用於迭代器塊中,其最本質的功能有2個:一是“依次”向列舉物件提供值,二是發出迭代結束訊號。這兩個功能對應的語句分別是yield return和yield break。
下面有2個小例子,分別沒有使用yield和有使用yield。先來看第一個,當我除錯時顯然執行到GetResult()方法時將會跳轉到方法內部並且執行完,接著再去執行輸出當前值語句。從結果可以看出第一個是0,說明返回的列舉數所在的位置在集合中是0,接著才是我想要的遍歷資料,也就是說只有呼叫MoveNext()後列舉數才會繼續向前移動得到下一個值,但是此時資料已全部載入到記憶體。
再來看第二個例子,當我除錯到GetResultByYield()方法時我想進入到這個方法內部結果發現直接執行下一句,根本就沒有進入到GetResultByYield()方法內部。此時發現result.Current是null,再加上前面根本都沒執行方法內部的程式碼,因此我猜測此時集合都是空的。繼續除錯,當執行MoveNext()方法時才去執行GetResultByYield(),接著執行到yield return隨即返回main()方法輸出列舉數所代表的集合中的值。
從上面可以看到只有呼叫MoveNext()需要用的時候才去執行方法來獲得結果,不用的時候並不會有任何結果。這個地方編譯器會有一個狀態機用來儲存迭代器的狀態,以保證for迴圈時是從上一次yield return停止的狀態繼續執行。這個過程就好比小方要喝一升的水,如果它用一個一升的杯子喝那麼他要準備一個一升的容器去飲水機裝滿一升的水。
如果小方喝到一半喝不完了,那接下來剩下的水則將被回收,這樣無論能不能喝完都必須準備好一升的水,就像第一個例子。現在讓杯子的容積縮小為0.2升,小方喝完一杯後再去飲水機去打水,每次只喝0.2升。這樣只有他要去喝的時候才去打水,如果他喝到一半不想喝了顯然浪費的水比第一種方式多,這就像第二個例子。最後根據條件不再需要資料便可呼叫yield return來跳出while迴圈,如果不寫yield break仍然可以正常結束迭代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/// /// 不使用yield的時候 /// class Program { static void Main(string[] args) { //得到一個迭代結果 var result = GetResult(); //輸出當前的值 Console.WriteLine(result.Current); Console.WriteLine("開始遍歷"); while (result.MoveNext()) { Console.WriteLine(result.Current); } Console.WriteLine("遍歷結束"); Console.ReadLine(); } //不使用yield來進行迭代 static IEnumeratorint> GetResult() { var arr = new int[] { 1, 6, 8, 12,15}; Listint> list = new Listint>(); foreach (int item in arr) { if (item 12) list.Add(item); } return list.GetEnumerator(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/// /// 使用yield關鍵字 /// class Program { static void Main(string[] args) { //得到一個迭代結果 var result = GetResultByYield(); //輸出當前的值 Console.WriteLine(result.Current); Console.WriteLine("開始遍歷"); while (result.MoveNext()) { Console.WriteLine(result.Current); } Console.WriteLine("遍歷結束"); Console.ReadLine(); } //使用yield來進行迭代 static IEnumerator GetResultByYield() { var arr = new int[] { 1,6,8,12,15}; foreach (var item in arr) { yield return item; if (item == 12) yield break; } } } |
輸出結果如下:
2.深入yield
將上面第二個例子放入Reflector工具中,便得到了下面三段程式碼。第一段是完整的Pragrom類的C#程式碼,第二段是d__0密封類的C#展開程式碼,第三段是GetResultByYield()方法的IL程式碼。在第一段程式碼中可以看到系統自動生成了一個d__0密封類,它裡面宣告瞭一些名字很奇怪的欄位,不過我們可以很清楚的看到這個類裡面有最重要的MoveNext()方法和Current屬性。
第二段程式碼則是這個密封類的C#展開程式碼,到這裡不知道讀者有沒有和我當初一樣的疑問:為什麼要自動生成一個密封類呢?答案就在第三段程式碼中,可以看到在GetResultByYield()方法中並沒有遍歷陣列,甚至都沒有看到建立陣列的newarr指令,而是newobj建立了d__0密封類的例項物件。這也正是前面除錯的時候為什麼根本就沒進去GetResultByYield()方法的原因,因為真真的實現程式碼是在密封類裡面的MoveNext()方法中。前面還提到yield是按需所取,因此需要一個狀態機來記錄每次yield return的狀態。
在MoveNext()方法中由於密封類建構函式傳進去的是一個0(在第三段程式碼中可以看到),因此第一次進入到MoveNext方法時this.__state=0。此時current欄位由於沒賦值因此就是null了。接著建立陣列並開始一個while迴圈(原來foreach就是while迴圈),在迴圈中給current欄位賦值並讓state欄位值為2,最後返回true。拿Current屬性時就是拿while迴圈中給current賦的值,再次進入這個方法內此時state等於2於是跳轉到Label_0090,也就是進入while語句塊中繼續迴圈,這就是按需所取的原理。當遇到yield break後會先執行Dispose釋放資源,再執行break語句跳出迴圈。可以看到上述這個過程就是一個狀態機,而這個密封類是為建立一個狀態機來生成的,現在我們自己也可以寫出一個狀態機了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
internal class Program { // Methods public Program(); private static IEnumerator GetResultByYield(); private static void Main(string[] args); // Nested Types [CompilerGenerated] private sealed class d__0 : IEnumeratorobject>, IEnumerator, IDisposable { // Fields private int 1__state; private object 2__current; public int[] 7__wrap4; public int 7__wrap5; public int[] 5__1; public int 5__2; // Methods [DebuggerHidden] public d__0(int 1__state); private void m__Finally3(); private bool MoveNext(); [DebuggerHidden] void IEnumerator.Reset(); void IDisposable.Dispose(); // Properties object IEnumeratorobject>.Current { [DebuggerHidden] get; } object IEnumerator.Current { [DebuggerHidden] get; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
private sealed class d__0 : IEnumeratorobject>, IEnumerator, IDisposable { // Fields private int 1__state; private object 2__current; public int[] 7__wrap4; public int 7__wrap5; public int[] 5__1; public int 5__2; // Methods [DebuggerHidden] public d__0(int 1__state) { this.1__state = 1__state; } private void m__Finally3() { this.1__state = -1; } private bool MoveNext() { try { switch (this.1__state) { case 0: this.1__state = -1; this.5__1 = new int[] { 1, 6, 8, 12, 15 }; this.1__state = 1; this.7__wrap4 = this.5__1; this.7__wrap5 = 0; while (this.7__wrap5 this.7__wrap4.Length) { this.5__2 = this.7__wrap4[this.7__wrap5]; this.2__current = this.5__2; this.1__state = 2; return true; Label_0090: this.1__state = 1; if (this.5__2 == 12) { this.System.IDisposable.Dispose(); break; } this.7__wrap5++; } this.m__Finally3(); break; case 2: goto Label_0090; } return false; } fault { this.System.IDisposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { switch (this.1__state) { case 1: case 2: this.m__Finally3(); break; } } // Properties object IEnumeratorobject>.Current { [DebuggerHidden] get { return this.2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return this.2__current; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.method private hidebysig static class [mscorlib]System.Collections.IEnumerator GetResultByYield() cil managed { .maxstack 1 .locals init ( [0] class ConsoleApplication1.Program/d__0 d__, [1] class [mscorlib]System.Collections.IEnumerator enumerator) L_0000: ldc.i4.0 L_0001: newobj instance void ConsoleApplication1.Program/d__0::.ctor(int32) L_0006: stloc.0 L_0007: ldloc.0 L_0008: stloc.1 L_0009: br.s L_000b L_000b: ldloc.1 L_000c: ret } |
3.單例模式
單例模式沒什麼好說的,當然如果深挖應該也是大有學問,其中我覺得比較好的一種寫法如下。單例模式的程式碼我看過多次不過卻沒怎麼寫,結果真真寫的時候再加上時間又有點緊最後寫的一塌糊塗。以後寫程式碼要興平氣和地去寫,急躁的狀態寫不出什麼好程式碼。當然總會有煩躁的時候,所以只能多寫程式碼來讓自己寫出高質量的程式碼成為一種習慣!
1 2 3 4 5 6 7 8 |
class A { private static A instance = new A(); public static A Instance { get { return A.instance; } } } |