上回講到,由於Lambda表達式的指針地址與一般函數的指針地址的區別,導致我在《Covid Lifestyle》的開發過程中,使用EventCenter作為事件中心進行函數監聽的添加和移除時出現了錯誤。這次我想講一下一個在Lambda表達式返回值類型上的坑。
在我的事件中心中,除了有添加事件監聽和移除事件監聽的方法外,還有一套用於實現事件的調用的廣播方法(代碼大致如下):
public static void Broadcast(LoneliNerdEventTypes evt, T1 arg1)
{
Delegate d;
if(evtDict.TryGetValue(evt, out d))
{
CallBack cb = d as CallBack;
if (cb != null) { cb(arg1); }
else { throw new Exception($”Broadcast Error : {evt}”); }
}
}
由於要調用的函數可能要接受參數,且類型未知,因此,廣播的方法也需要接受泛型參數。那如果我們想做一個回調,把lambda表達式作為參數,在調用廣播方法時傳遞進去,然後讓在廣播所調用的函數體內再調用這個作為參數傳過去的方法,可以嗎?
如果直接用lambda表達式作為參數的話,就像下面那樣,答案是不行。
EventCenter.Broadcast(EventTypes.Show, () => DoSomething());
使用這個方式來傳參,將會報出這樣的錯誤:
報錯信息:錯誤 CS0411 方法 ‘LoneliNerdEventCenter.Broadcast<T1>(LoneliNerdEventTypes, T1)’ 的類型引數不可從使用方式推斷。請嘗試明確地指定類型引數。
為甚麼呢?這就跟lambda的返回值類型有關了。
大部分的資料都希望以更簡單直接的方式來告訴我們lambda的應用,因此,很多時候他們會直接把lambda表達式說成是委托的簡寫,但是實際上並不完全是這樣的。我們看一下Microsoft關於Lambda Expression 的線上文檔,裡面說到:
任何 Lambda 運算式可轉換成委派型別。 Lambda 運算式可以轉換成的委派型別,是由其參數和傳回值的型別所定義。 如果 Lambda 運算式不會傳回值,則其可轉換成其中一個 Action 委派型別;否則可轉換成其中一個 Func 委派型別。
一點總結
也就是說Lambda表達式雖然可以被主動轉換成一個委托類型(Action/Func,視乎該運算式本身有沒有Return Value),但它實際上更多的是一種「使用方式」(即使Lambda經常被認為本質上是一個委托)。
所以,Lambda表達式本身其實並不歸屬於任何類型,因此也無法傳入一個接受泛型參數的函數之中。
因此,如果我們想把函數作為參數傳給一個接受泛型參數的函數裡的話,還是不能偷懶,要麼就事先聲明一個Action/Delegate/Function,或者其他自定義的委托類型的變量,然後進行賦值(賦值時就可以用lambda了),最後再將這個委托類型的變量傳遞出去;要麼就在形參類型從泛型改為委托,這樣函數在接受參數時也會直接將傳進來的Lambda表達式直接強轉成委托,就像下面的代碼一樣。
1. 改變形參
public static void Broadcast(LoneliNerdEventTypes evt, Action arg1)
{
Delegate d;
if(evtDict.TryGetValue(evt, out d))
{
CallBack<Action> cb = d as CallBack<Action>;
if (cb != null) { cb(arg1); }
else { throw new Exception($”Broadcast Error : {evt}”); }
}
}
2. 事先聲明
//Normal way
Action defaultControl = DefaultPanelControl;
LoneliNerdEventCenter.Broadcast(LoneliNerdEventTypes.StartTimeCounting, defaultControl);
//Lambda Expression way
Action defaultControl_Lambda = () => ControlPanelReactor(ShowingObjectType.NONE, (sbyte)-1); LoneliNerdEventCenter.Broadcast(LoneliNerdEventTypes.StartTimeCounting, defaultControl_Lambda);