很多書和文章都希望能夠從原理層面把代理機制講清楚,從C和C++的指針講到線程安全,從通用類庫講到事件驅動,對於高端玩家,這是很有必要的,但對於初學者,就容易越看越暈,從雲山霧罩,到雲深不知處,本來只有一個概念不懂,現在變成N個了,最後只能投降,不了了之。
這個概念確實不太容易理解。下面嘗試根據我的理解來說明。
我們在開發期間最常用的方法所傳遞的參數,一般情況下是某個類型的變量,比如:
public long op(long a,long b) { return a + b; }
現在我給你出個題,你幫我向方法A中傳遞一個方法B,並在A中執行B。
是不是不會了?
對部分開發者來講,這確實是一個新的概念,或者說是一個新的操作方式,以前沒這樣做過。
聰明的你可能已經想到了,對,在C#中,此時要用到代理。
按照方法簽名的定義方式,要傳遞的參數應該是“某個類型 形參”,比如上例中的long a。那麼要傳遞的這個方法的“類型”是什麼呢?
就是代理,delegate。
當你要向一個方法A傳遞另一個方法B的時候,要爲這個B方法單獨定義一個“類型”,它要求的有三個要素,參數類型、個數、返回值,只要符合這三項的,都可以傳。
比如要向方法A傳遞上例中的op(long a,long b)方法,就要像下面這樣定義:
public delegate long TwoLongsOp(long a, long b);
這時,一個新的“類型”就產生了,所有符合“兩個long型參數、返回值爲long”特點的方法,都符合這個類型定義。
現在就可以解答我上面出的題了,使用這個類型向方法A傳遞方法B。
首先要有方法A:
public long Atest(TwoLongsOp a) { return 0; }
顯而易見,這個方法什麼也沒做,但它可以傳一個代理了,離成功近了一點兒。
下一步開始調用傳過來的參數a,要注意,這個參數是個帶有兩個參數和返回值的方法,調用它也就是執行它。
怎麼調用呢?
就像在方法A中調用其它方法一樣。
public long Atest(TwoLongsOp a) { var r = a(2,3); return r; }
到這裏,程序是可以編譯的,符合編譯規則,但沒什麼用。因爲我們還沒有定義方法B,也就是方法A的那個“實參”。
下面我們來定義方法B,方法B一定要符合delegate所定義的格式,參數、返回值,一定要一致:
public long Btest(long a,long b) { //其實這裏寫點什麼都行,不一定非得是對a和b的操作,只是對入參進行操作是對它們最大的尊重。 return a + b; }
接下來就可以正式調用了,噹噹噹當~~向方法A中傳入方法B:
public ActionResult Index() { ViewBag.Message = Atest(Btest).ToString(); return View(); }
我們再定義一個C:
public long Ctest(long a, long b) { return a * b; }
接下來可以這樣調用:
public ActionResult Index() { ViewBag.Message1 = Atest(Btest).ToString(); ViewBag.Message2 = Atest(Ctest).ToString(); return View(); }
有一點思考一下,代理與方法B和方法C,到底是B和C被代理限制,還是代理爲B和C服務?
想必有人會問,這樣操作的意義是什麼,直接調用Btest不就可以了?
對,在本例中,是的。但在很多團隊合作的情況下,A不是你寫的,甚至不是你們公司寫的,暴露給你的只是一個方法名,要求傳入符合方法簽名要求的方法。或者你是寫A的人,要給寫B的人留下餘量。
“委託”或者“代理”一詞應該怎樣理解呢?
從方法的角度來講,對於參數,它只認識“類型 參數”的模式,所以,方法的“類型”是什麼呢?它沒有內置的類型,所以無法直接傳遞。既然不能直接向A傳遞B,就需要一個“中間人”,定義一個新的“類型”,A和B委託deleate定義的那個東西作爲媒介,把B傳給A。這個新定義的類型,服從A和B的約定(參數類型、個數、返回值)。所以,委託的定義是爲A、B、C服務,它們有需求,所以需要一個合格的中間人。
有些事情只能是在開發過程中遇到了纔會意識到它的作用和價值,在遇到之前,不太容易理解。本例權當是一次智力遊戲。
念念不忘,必有迴響,誠不我欺,共勉。