パラメーターが1つしかない場合でも、ラムダ構文を好む理由はありますか?


14
List.ForEach(Console.WriteLine);

List.ForEach(s => Console.WriteLine(s));

私にとって、違いは純粋に化粧品ですが、一方が他方よりも好まれる微妙な理由はありますか?


私の経験では、2番目のバージョンが望ましいと思われるときはいつでも、それは通常、問題のメソッドの命名が不十分だったためでした。
ローマンライナー

回答:


23

ILSpyでコンパイルされたコードを見ると、実際には2つの参照に違いがあります。このような単純なプログラムの場合:

namespace ScratchLambda
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    internal class Program
    {
        private static void Main(string[] args)
        {
            var list = Enumerable.Range(1, 10).ToList();
            ExplicitLambda(list);
            ImplicitLambda(list);
        }

        private static void ImplicitLambda(List<int> list)
        {
            list.ForEach(Console.WriteLine);
        }

        private static void ExplicitLambda(List<int> list)
        {
            list.ForEach(s => Console.WriteLine(s));
        }
    }
}

ILSpyは次のように逆コンパイルします。

using System;
using System.Collections.Generic;
using System.Linq;
namespace ScratchLambda
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<int> list = Enumerable.Range(1, 10).ToList<int>();
            Program.ExplicitLambda(list);
            Program.ImplicitLambda(list);
        }
        private static void ImplicitLambda(List<int> list)
        {
            list.ForEach(new Action<int>(Console.WriteLine));
        }
        private static void ExplicitLambda(List<int> list)
        {
            list.ForEach(delegate(int s)
            {
                Console.WriteLine(s);
            }
            );
        }
    }
}

両方のIL呼び出しスタックを見ると、Explicit実装にはさらに多くの呼び出しがあります(生成されたメソッドを作成します)。

.method private hidebysig static 
    void ExplicitLambda (
        class [mscorlib]System.Collections.Generic.List`1<int32> list
    ) cil managed 
{
    // Method begins at RVA 0x2093
    // Code size 36 (0x24)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0006: brtrue.s IL_0019

    IL_0008: ldnull
    IL_0009: ldftn void ScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
    IL_000f: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
    IL_0014: stsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'

    IL_0019: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_001e: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
    IL_0023: ret
} // end of method Program::ExplicitLambda


.method private hidebysig static 
    void '<ExplicitLambda>b__0' (
        int32 s
    ) cil managed 
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x208b
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: call void [mscorlib]System.Console::WriteLine(int32)
    IL_0006: ret
} // end of method Program::'<ExplicitLambda>b__0'

暗黙の実装はより簡潔です:

.method private hidebysig static 
    void ImplicitLambda (
        class [mscorlib]System.Collections.Generic.List`1<int32> list
    ) cil managed 
{
    // Method begins at RVA 0x2077
    // Code size 19 (0x13)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldnull
    IL_0002: ldftn void [mscorlib]System.Console::WriteLine(int32)
    IL_0008: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
    IL_000d: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
    IL_0012: ret
} // end of method Program::ImplicitLambda

これはクイックスクラッチプログラムからのコードのリリースビルドであるため、さらなる最適化の余地があることに注意してください。ただし、これはVisual Studioのデフォルトの出力です。
Agent_9191

2
+1これは、ラムダ構文が実際に生のメソッド呼び出しを匿名関数<i>理由なし</ i>でラップしているためです。これはまったく意味がないため、使用可能な場合は、Func <>パラメーターとしてrawメソッドグループを使用する必要があります。
エドジェームズ

わあ、あなたは研究のために緑色のカチカチ音をたてます!
ベンジョー

2

は一般的にラムダ構文好むでしょう。それを見ると、タイプが何であるかがわかります。が表示さConsole.WriteLineれたら、IDEにそのタイプを尋ねる必要があります。もちろん、この些細な例では明らかですが、一般的なケースではそれほど多くはないかもしれません。


必要な場合との一貫性のために、labmda構文を好むでしょう。
bunglestink

4
私はC#の人ではありませんが、ラムダ(JavaScript、Scheme、Haskell)で使用した言語では、おそらく反対のアドバイスを与えるでしょう。スタイルが言語に依存していることを示していると思います。
ティコンジェルビス

どのようにタイプを教えてくれますか?確かにあなたはラムダのパラメータの型が、約明示することができ、そのはるかにこのような状況で行わそれを行うための一般的な、そしてイマイチから
JK。

1

あなたが与えた2つの例では、あなたが言うときにそれらは異なります

List.ForEach(Console.WriteLine) 

実際にForEachループにWriteLineメソッドを使用するよう指示しています

List.ForEach(s => Console.WriteLine(s));

foreachが呼び出すメソッドを実際に定義し、そこで何を処理するかを指定しています。

シンプルなライナーの場合、呼び出すメソッドが既に呼び出されているメソッドと同じシグネチャを持っている場合、ラムダを定義しない方がいいでしょう、もう少し読みやすいと思います。

互換性のないラムダを持つメソッドの場合、それらが過度に複雑にならないことを前提として、間違いなく良い方法です。


1

最初の行を好む理由は非常に強力です。

すべてのデリゲートにはTargetプロパティがあり、インスタンスがスコープ外になった後でも、デリゲートはインスタンスメソッドを参照できます。

public class A {
    public int Data;
    public void WriteData() {
        Console.WriteLine(this.Data);
    }
}

var a1 = new A() {Data=4};
Action action = a1.WriteData;
a1 = null;

nullであるa1.WriteData();ため、呼び出すことa1ができません。しかし、我々は呼び出すことができますaction問題なくデリゲートを、そしてそれが印刷されます4ので、actionメソッドが呼び出されるべきでインスタンスへの参照を保持しています。

匿名メソッドがインスタンスコンテキストでデリゲートとして渡される場合、デリゲートはそれが含まれているクラスへの参照を保持します。

public class Container {
    private List<int> data = new List<int>() {1,2,3,4,5};
    public void PrintItems() {
        //There is an implicit reference to an instance of Container here
        data.ForEach(s => Console.WriteLine(s));
    }
}

この特定のケースでは.ForEach、デリゲートが内部に格納されていないと想定するのが合理的です。つまり、インスタンスContainerとそのすべてのデータがまだ保持されていることを意味します。しかし、それを保証するものはありません。デリゲートを受け取るメソッドは、デリゲートとインスタンスを無期限に保持する場合があります。

一方、静的メソッドには、参照するインスタンスはありません。以下は、のインスタンスへの暗黙的な参照を持ちませんContainer

public class Container {
    private List<int> data = new List<int>() {1,2,3,4,5};
    public void PrintItems() {
        //Since Console.WriteLine is a static method, there is no implicit reference
        data.ForEach(Console.WriteLine);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.