[C#/Unity] Lambda表達式的返回值類型

        上回講到,由於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);