[C#] ラムダ式における見えない new の改善 (.NET 7 Preview 1版)

更新: 2022-03-04 (金) 投稿: 2022-02-24 (木)

早6年前、neue大先生の記事で得た知見ですが、.NET 7 Preview 1 でコンパイル後の結果が違うことに偶然気付いたので確認しました。

追記2022/03/04
roslyn/Language Feature Status にそれらしきIssueがありました。
Use a cached delegate for method group conversion · Issue #5835 · dotnet/roslyn

環境

  • .NET 6.0.200
  • .NET 7 Preview 1
  • SharpLab

結果

検証用のコードは、neue大先生のコードを利用させていただきます😄

static int DoubleStatic(int x)
{
    return x * 2;
}

int DoubleInstance(int x)
{
    return x * 2;
}

void Run()
{
    var two = int.Parse("2");

    Enumerable.Range(1, 1).Select(DoubleStatic);           // 1
    Enumerable.Range(1, 2).Select(DoubleInstance);         // 2
    Enumerable.Range(1, 3).Select(x => x * 2);             // 3
    Enumerable.Range(1, 4).Select(x => x * two);           // 4
    Enumerable.Range(1, 5).Select(x => DoubleStatic(x));   // 5
    Enumerable.Range(1, 6).Select(x => DoubleInstance(x)); // 6
}
Noラムダ式.NET 6.NET 7 Preview 1 (3b32fc3)結果コード
1Select(DoubleStatic)❌毎回new✅初回キャッシュ❌5を推奨。キャッシュ化されたが、staticメソッドは遅いSharpLab
2Select(DoubleInstance)❌毎回new<-同じ6と同じ、自分でキャッシュ必要SharpLab
3Select(x => x * 2)✅初回キャッシュ<-同じ✅GoodSharpLab
4Select(x => x * two)🔥毎回new 2個以上<-同じ🔥クロージャ使用のためゴミ発生SharpLab
5Select(x => DoubleStatic(x))✅初回キャッシュ<-同じ✅Good。ReSharperが1のコードを提案するので注意SharpLab
6Select(x => DoubleInstance(x))❌毎回new<-同じ5と違い、instanceメソッドは自分でキャッシュ必要SharpLab

1の結果のみが変わり、キャッシュ化に改善されていました。
しかし、5と異なり instanceメソッドを生成してくれないので、まだ推奨コードは今までと変わらなそうです。

デリゲート呼び出しは、staticメソッドよりinstanceメソッドの方は早いようなのです。
計測はしていませんが、以下参考にしました。
[雑記] デリゲートの内部 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
静的メソッドをデリゲート化した場合の呼び出し負荷、思ったよりもでかかった

まとめ

  • 1 は 5 を使おう。
  • 2, 6 は同じ。必要なら自前でキャッシュしよう。
  • 3, 5 はOK。
  • 4 はクロージャなので基本避けれない。

詳細

1. Select(DoubleStatic)

.NET 6

private static int DoubleStatic(int x)
{
    return x * 2;
}

private void Run()
{
    Enumerable.Select(Enumerable.Range(1, 1), new Func<int, int>(DoubleStatic));
}

.NET 7.0 Preview 1 で改善💡

[CompilerGenerated]
private static class <>O
{
    public static Func<int, int> <0>__DoubleStatic;
}

private static int DoubleStatic(int x)
{
    return x * 2;
}

private void Run()
{
    Enumerable.Select(Enumerable.Range(1, 1), <>O.<0>__DoubleStatic ?? (<>O.<0>__DoubleStatic = new Func<int, int>(DoubleStatic)));
}

2. Select(DoubleInstance)

.NET 6 と .NET 7.0 Preview 1

private int DoubleInstance(int x)
{
    return x * 2;
}

private void Run()
{
    Enumerable.Select(Enumerable.Range(1, 2), new Func<int, int>(DoubleInstance));
}

3. Select(x => x * 2)

.NET 6 と .NET 7.0 Preview 1

[Serializable]
[CompilerGenerated]
private sealed class <>c
{
    public static readonly <>c <>9 = new <>c();

    public static Func<int, int> <>9__0_0;

    internal int <Run>b__0_0(int x)
    {
        return x * 2;
    }
}

private void Run()
{
    Enumerable.Select(Enumerable.Range(1, 3), <>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Func<int, int>(<>c.<>9.<Run>b__0_0)));
}

4. Select(x => x * two)

.NET 6 と .NET 7.0 Preview 1

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public int two;

    internal int <Run>b__0(int x)
    {
        return x * two;
    }
}

private void Run()
{
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.two = int.Parse("2");
    Enumerable.Select(Enumerable.Range(1, 4), new Func<int, int>(<>c__DisplayClass0_.<Run>b__0));
}

5. Select(x => DoubleStatic(x))

.NET 6 と .NET 7.0 Preview 1

[Serializable]
[CompilerGenerated]
private sealed class <>c
{
    public static readonly <>c <>9 = new <>c();

    public static Func<int, int> <>9__1_0;

    internal int <Run>b__1_0(int x)
    {
        return DoubleStatic(x);
    }
}

private static int DoubleStatic(int x)
{
    return x * 2;
}

private void Run()
{
    Enumerable.Select(Enumerable.Range(1, 5), <>c.<>9__1_0 ?? (<>c.<>9__1_0 = new Func<int, int>(<>c.<>9.<Run>b__1_0)));
}

6. Select(x => DoubleInstance(x))

.NET 6 と .NET 7.0 Preview 1

private int DoubleInstance(int x)
{
    return x * 2;
}

private void Run()
{
    Enumerable.Select(Enumerable.Range(1, 6), new Func<int, int>(<Run>b__1_0));
}

[CompilerGenerated]
private int <Run>b__1_0(int x)
{
    return DoubleInstance(x);
}

感謝

更新履歴

  • 2022/03/04
    • Issue へのリンク追記。
    • 1のstaticメソッドのデリゲートは遅い件の補足追記。
更新: 2022-03-04 (金) 投稿: 2022-02-24 (木)