[.NET]延遲執行(Deferred Execution) 簡單概念實作
	前言
	最近迷上了研究LINQ底層實作的東西,希望自己可以不透過反組譯,從LINQ可以達到的功能以及一些特性,去反思如果是自己設計一套這樣的framework,該怎麼著手。
	
	這兩天則與小朱和Bill叔討論到LINQ to Object裡面的延遲執行查詢(請參考:LINQ 查詢簡介 (C#)裡面的查詢執行)如果要自己設計,該怎麼做。這實在是個很有趣的議題,從目的、委派、編譯器到覆寫operator,一堆天馬行空的想法,就是想自己組出一樣的效果。(說到這,要更佩服Bill叔的熱忱,我們下班的時候還用手機在講可以用哪些東西,可能是用哪些東西來組合成『延遲執行』的想法,晚上睡覺前Bill叔就寫出一版雛形來印證想法了,害我躺在床上睡不著,爬起來把我腦袋中的想法,寫成sample code)
	
	概念
	我自己想了幾個簡單的概念,或許可以當作一個最陽春的延遲執行的基底。
- 使用委派:要延遲執行,基本需求描述就是『等到需要執行的時候,把剛剛要跑的一大串東西,做一次跑完』,這代表著『呼叫某個要延遲執行的function時,要把這個方法內容暫存起來』,第一個想法,當然就是使用委派。
 - 使用一個集合,來存委派跟參數集合
 - 使用Action<T>,來當作function的暫存轉接。
 - 每個function會回傳自己這個instance回去,以達到LINQ裡面的function chain的味道。
 - 當被實際執行後,要把剛剛暫存的delegate集合清空。(如果稱做expression tree,會不會更專業一點 ^___^)
 
	
	簡單實作
- 先針對單一型別來設計,這邊舉例的是int,未來進階版就可以改成泛型。(例如IEnumerable<T>裡面的T)
 - 針對單數類別設計,先用單純的方式設計,未來進階版就可以改成集合。(例如IEnumerable<T>的IEnumerable)
 - 設計兩個function,分別為Add(int), Minus(int),當呼叫這兩個function時並不直接執行與回傳結果。
 - 當呼叫InvokeExpression()時,才將需要執行的Add()與Minus()做一次執行。
 
	
	接下來我們來看程式碼,這邊的例子是設計一個類別叫做Joey:
    public class Joey
    {
        struct MyPair
        {
            public Delegate expression;
            public object[] parameters;
        }
        private List<MyPair> myExpression = new List<MyPair>();
        public int MyValue { get; private set; }
        public Joey(int initialValue)
        {
            this.MyValue = initialValue;
        }
        public Joey Add(int value)
        {
            Action<int> mydelegate = (x) =>
            {
                this.MyValue += x;
                Console.Write(" + {0}", x.ToString());
            };
            var o = new MyPair { expression = mydelegate, parameters = new object[] { value } };
            this.myExpression.Add(o);
            return this;
        }
        public Joey Minus(int value)
        {
            Action<int> mydelegate = (x) =>
            {
                this.MyValue -= x;
                Console.Write(" - {0}", x.ToString());
            };
            var o = new MyPair { expression = mydelegate, parameters = new object[] { value } };
            this.myExpression.Add(o);
            return this;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// <remarks>可改為event trigger</remarks>
        public int InvokeExpression()
        {
            Console.Write(this.MyValue.ToString());
            foreach (var item in this.myExpression)
            {
                item.expression.DynamicInvoke(item.parameters);
            }
            //清空
            this.myExpression.Clear();
            Console.WriteLine();
            return this.MyValue;
        }
    }
程式碼其實頗單純,都只是上面提到的簡單概念應用。內容說明如下:
- struct MyPair:用來存放每一次的Delegate,以及該Delegate所需參數的結構。
 - private List<MyPair> myExpression:上面提到的暫存Delegate的集合。
 - MyValue property與建構式:給外界取用的結果值,在建構式時,給一個初始值。透過這個property我們可以判別Expression到底實際執行了沒。
 - Add()與Minus():使用Action<int>來當作該function的『影子』,也就是把這次實際要執行的function內容,用Action<T>暫存起來,並加入整個instance之後要執行的Expression集合中。
 - InvokeExpression():把剛剛暫存的Expression集合,一個一個叫出來配合參數調用委派。調用完後清除Expression集合,避免影響到這個instance之後其他的Expression。
 
	
	使用場景範例
    class Program
    {
        static void Main(string[] args)
        {
            var joey = new Joey(1);
            var o = joey.Add(2).Add(3).Add(4).Minus(5);
            var resultWithoutInvoke = o.MyValue;
            Console.WriteLine("尚未執行的結果:{0}", resultWithoutInvoke);
            o.InvokeExpression();
            var result = o.MyValue;
            Console.WriteLine("執行後的結果:{0}", result);
            Console.ReadKey();
            Console.WriteLine();
            var o2 = o.Add(20).Add(30).Add(40).Minus(50);
            var resultWithoutInvoke2 = o2.MyValue;
            Console.WriteLine("尚未執行第二次前的結果:{0}", resultWithoutInvoke2);
            o2.InvokeExpression();
            var result2 = o2.MyValue;
            Console.WriteLine("第二次執行後的結果:{0}", result2);
            Console.WriteLine();
        }
    }
- 一開始給初始值1。
 - 呼叫Add(2), Add(3), Add(4), Minus(5)後,可以發現MyValue的值,仍然是1,沒有改變。
 - 呼叫InvokeExpression(),可以看到MyValue的值變成5了,且畫面有呈現出每一個Expression執行的過程。
 - 當已經被呼叫過一次InvokeExpression(),接著我們在用剛剛的instance,呼叫Add(20), Add(30), Add(40), Minus(50),呼叫完後,檢查值仍為第一次結果的值:5。
 - 再呼叫一次InvokeExpression(),檢查結果,如同預期的45。
 
	
	執行結果畫面
	![]()
	
	結論
	這只是最簡單的概念,也不會是最佳化的設計。但有了這樣的基底和概念,之後還有很多東西可以玩,例如:
- 怎麼把int改成泛型
 - 怎麼把Joey改成集合
 - 怎麼把Joey改成介面
 - 怎麼把方法改成擴充方法
 - invoke的方法怎麼透過event去trigger發動
 - 怎麼把expression集合,改成expression tree或其他更好、更彈性的資料結構
 - 怎麼把expression改成由資料發動(用資料寫程式,用程式寫資料)
 - invoke的方式是否有調整空間,例如遞迴,MapFunction之類的方式。
 
	
	原本想寫的系列文,是用自己的方式建構出LINQ上面那堆美妙的API,透過那樣的過程來讓自己訓練怎麼從最基本的元素組成這麼漂亮的framework。
	
	不過老狗叔一個link就把我打回原形了,C# in Depth的作者已經寫了這個系列了,請參考:Reimplementing LINQ to Objects: Part 45 - Conclusion and List of Posts,看了幾篇更覺得自己是水井國民啊…大概對每一個function的基本概念都對,但是其他要考量的點真的是多到不行,例如這篇文章介紹的『延遲執行』。
	
	我不認為我幾年內,能寫出像Jon Skeet這麼好、這麼細、這麼深的書或文章,所以還是快樂的當個寄生蟲學習者,仔細的咀嚼大師們嘔心瀝血之作吧。不過,看這種文章可以一直給腦袋的想法帶來衝擊,那種衝擊感真的挺過癮的…
	
	Sample Project:DeferredExecution.zip
blog 與課程更新內容,請前往新站位置:http://tdd.best/
