c# - parameter - 何時不使用lambda表達式




wpf lambda expression (6)

Stack Overflow正在回答很多問題,成員們指定如何使用lambda表達式解決這些現實世界/時間問題。

我們是否過度使用它,我們是否正在考慮使用lambda表達式對性能的影響?

我發現了一些文章,探討了lambda與匿名委託vs for / foreach循環對不同結果的性能影響

  1. 匿名代表與Lambda表達式與函數調用性能
  2. foreach與List.ForEach的表現
  3. .NET / C#循環性能測試(FOR,FOREACH,LINQ和Lambda)
  4. DataTable.Select比LINQ更快

選擇合適的解決方案時,評估標準應該是什麼? 除了明顯的原因,它使用lambda時更簡潔的代碼和可讀性。


Lambda表達式很酷。 在舊的delegate語法中,它們有一些優點,例如,它們可以轉換為匿名函數或表達式樹,參數類型是從聲明中推斷出來的,它們更清晰,更簡潔等等。我認為不使用lambda表達式沒有實際價值當你需要匿名功能時。 早期風格的一個不那麼大的優點是,如果不使用它們,你可以完全省略參數聲明。 喜歡

Action<int> a = delegate { }; //takes one argument, but no argument specified

當你必須聲明一個什麼都不做的空委託時,這很有用,但這不是一個不能使用lambdas的強大理由。

Lambdas允許您編寫快速匿名方法。 現在,這使得lambdas在匿名方法變得毫無意義的地方毫無意義,即命名方法更有意義。 在命名方法上 ,匿名方法可能是不利的(本身不是lambda表達式,但是從那時起lambdas廣泛代表匿名方法它是相關的):

  1. 因為它往往導致邏輯重複(通常很難重用)

  2. 當沒有必要寫一個,如:

    //this is unnecessary 
    Func<string, int> f = x => int.Parse(x);
    
    //this is enough
    Func<string, int> f = int.Parse;
    
  3. 因為寫匿名迭代器塊是不可能的。

    Func<IEnumerable<int>> f = () => { yield return 0; }; //impossible
    
  4. 因為遞歸的lambdas需要更多的怪癖,比如

    Func<int, int> f = null;
    f = x => (x <= 1) ? 1 : x * f(x - 1);
    
  5. 好吧,既然反思有點混亂,但那是沒有實際意義的不是嗎?

除了第3點,其餘的不是不使用 lambdas的強烈理由。

另請參閱此thread ,了解Func/Action委託的不利之處,因為它們通常與lambda表達式一起使用。


lambda只是簡單地將其參數直接傳遞給另一個函數。 不要為函數應用程序創建lambda。

例:

var coll = new ObservableCollection<int>();
myInts.ForEach(x => coll.Add(x))

更好的是:

var coll = new ObservableCollection<int>();
myInts.ForEach(coll.Add)

主要的例外是C#的類型推斷由於某種原因而失敗(並且有很多次是真的)。


即使我將重點放在第一點,我首先在整個性能問題上給出2美分。 除非差異很大或者使用密集,否則通常我不打擾微秒,添加時不會對用戶產生任何明顯的差異。 我強調在考慮非密集調用方法時我只是不在乎。 我在哪裡有特殊的性能考慮因素是我設計應用程序本身的方式。 我關心緩存,關於線程的使用,關於調用方法的聰明方法(是否進行多次調用或嘗試只進行一次調用),是否匯集連接等等。事實上我通常都不喜歡不是關注原始性能,而是關注可擴展性。 我不在乎單個用戶是否只需要一小片納秒就可以運行得更好,但我非常關心能夠在沒有註意到影響的情況下為大量同時用戶加載系統。

話雖如此,這裡是我對第1點的看法。我喜歡匿名方法。 它們給了我極大的靈活性和代碼優雅。 關於匿名方法的另一個重要特性是它們允許我直接使用容器方法中的局部變量(從C#角度來看,當然不是從IL角度來看)。 他們經常為我提供大量代碼。 我什麼時候使用匿名方法? Evey單一時間在其他地方不需要我需要的代碼。 如果它在兩個不同的地方使用,我不喜歡複製粘貼作為重用技術,所以我將使用普通的'委託'。 所以,就像shoosh回答的那樣,重複代碼並不好。 理論上沒有性能差異,因為anonyms是C#技巧,而不是IL的東西。

我對匿名方法的大部分內容都適用於lambda表達式,因為後者可以用作表示匿名方法的緊湊語法。 我們假設以下方法:

public static void DoSomethingMethod(string[] names, Func<string, bool> myExpression)
{
    Console.WriteLine("Lambda used to represent an anonymous method");
    foreach (var item in names)
    {
        if (myExpression(item))
            Console.WriteLine("Found {0}", item);
    }
}

它接收一個字符串數組,對於每個字符串,它將調用傳遞給它的方法。 如果該方法返回true,則會顯示“Found ...”。 您可以通過以下方式調用此方法:

string[] names = {"Alice", "Bob", "Charles"};
DoSomethingMethod(names, delegate(string p) { return p == "Alice"; });

但是,您也可以通過以下方式調用它:

DoSomethingMethod(names, p => p == "Alice");

IL之間沒有差別,因為使用Lambda表達式的IL更易讀。 再一次,沒有性能影響,因為這些都是C#編譯器技巧(不是JIT編譯器技巧)。 正如我不認為我們過度使用匿名方法一樣,我不認為我們過度使用Lambda表達式來表示匿名方法。 當然,相同的邏輯適用於重複的代碼:不要做lambdas,使用常規委託。 還有其他限制會導致您回到匿名方法或普通代理,例如out或ref參數傳遞。

Lambda表達式的其他好處是完全相同的語法不需要表示匿名方法。 Lambda表達式也可以表示......你猜對了,表達式。 採用以下示例:

public static void DoSomethingExpression(string[] names, System.Linq.Expressions.Expression<Func<string, bool>> myExpression)
{
    Console.WriteLine("Lambda used to represent an expression");
    BinaryExpression bExpr = myExpression.Body as BinaryExpression;
    if (bExpr == null)
        return;
    Console.WriteLine("It is a binary expression");
    Console.WriteLine("The node type is {0}", bExpr.NodeType.ToString());
    Console.WriteLine("The left side is {0}", bExpr.Left.NodeType.ToString());
    Console.WriteLine("The right side is {0}", bExpr.Right.NodeType.ToString());
    if (bExpr.Right.NodeType == ExpressionType.Constant)
    {
        ConstantExpression right = (ConstantExpression)bExpr.Right;
        Console.WriteLine("The value of the right side is {0}", right.Value.ToString());
    }
 }

注意略有不同的簽名。 第二個參數接收表達式而不是委託。 調用此方法的方法是:

DoSomethingExpression(names, p => p == "Alice");

這與使用lambda創建匿名方法時的調用完全相同。 這裡的不同之處在於我們不是創建匿名方法,而是創建表達式樹。 正是由於這些表達式樹,我們才能將lambda表達式轉換為SQL,這就是Linq 2 SQL所做的,例如,而不是在引擎中為Where,Select等等每個子句執行東西。無論您是創建匿名方法還是發送表達式,調用語法都是相同的。


好吧,當我們討論委託使用時,lambda和匿名方法之間應該沒有任何區別 - 它們是相同的,只是語法不同。 從運行時的角度來看,命名方法(用作委託)也是相同的。 然後,區別在於使用委託與內聯代碼之間 - 即

list.ForEach(s=>s.Foo());
// vs.
foreach(var s in list) { s.Foo(); }

(我希望後者更快)

同樣,如果您正在談論內存中對像以外的任何內容,lambdas是維護類型檢查(而不是一直解析字符串)方面最強大的工具之一。

當然,有些情況下,帶有代碼的簡單foreach將比LINQ版本更快,因為調用的次數會更少,並且調用會花費很少但可測量的時間。 但是,在許多情況下,代碼根本不是瓶頸,而更簡單的代碼(特別是對於分組等)的價值遠遠超過幾納秒。

另請注意,在.NET 4.0中,還有其他Expression節點 ,例如循環,逗號等。語言不支持它們,但運行時支持它們。 我只是為了完整性而提到這一點:我當然不是說你應該使用foreach手工Expression構造!


我會說性能差異通常很小(在循環的情況下,顯然,如果你看看第二篇文章的結果(順便說一句,Jon Skeet here有類似的文章)),你幾乎不應該選擇僅出於性能原因的解決方案,除非您正在編寫一個軟件,其中性能絕對最重要的非功能性要求,並且您必須進行微優化。

什麼時候選擇? 我想這取決於情況,但也取決於人。 舉個例子,有些人在普通的foreach循環中使用List.Foreach。 我個人更喜歡後者,因為它通常更具可讀性,但我是誰來反對這一點呢?


我的答案不會受歡迎。

我相信Lambda的99%總是更好的選擇,原因有三個。

首先,假設您的開發人員很聰明,絕對沒有錯。 其他答案有一個潛在的前提,即每個開發人員,但你是愚蠢的。 不是這樣。

其次,Lamdas(等)是一種現代語法 - 明天它們將比現在更普遍。 您的項目代碼應該來自當前和新興的約定。

第三,編寫代碼“老式的方式”對你來說似乎更容易,但對編譯器來說並不容易。 這很重要,隨著編譯器的重新編譯,遺留方法幾乎沒有機會得到改進。 Lambdas(等)依靠編譯器來擴展它們可以受益,因為編譯器隨著時間的推移更好地處理它們。

總結一下:

  1. 開發人員可以處理它
  2. 每個人都這樣做
  3. 有未來的潛力

我再次知道這不是一個受歡迎的答案。 相信我“簡單就是最好”也是我的口頭禪。 維護是任何來源的重要方面。 我知道了。 但我認為我們用一些陳詞濫調的經驗法則掩蓋了現實。

// 傑瑞





anonymous-methods