更新: 2022-03-04 (金)
投稿: 2022-02-24 (木)
[C#] ラムダ式における見えない new の改善 (.NET 7 Preview 1版)
早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) | 結果 | コード |
---|---|---|---|---|---|
1 | Select(DoubleStatic) | ❌毎回new | ✅初回キャッシュ | ❌5を推奨。キャッシュ化されたが、staticメソッドは遅い | SharpLab |
2 | Select(DoubleInstance) | ❌毎回new | <-同じ | 6と同じ、自分でキャッシュ必要 | SharpLab |
3 | Select(x => x * 2) | ✅初回キャッシュ | <-同じ | ✅Good | SharpLab |
4 | Select(x => x * two) | 🔥毎回new 2個以上 | <-同じ | 🔥クロージャ使用のためゴミ発生 | SharpLab |
5 | Select(x => DoubleStatic(x)) | ✅初回キャッシュ | <-同じ | ✅Good。ReSharperが1のコードを提案するので注意 | SharpLab |
6 | Select(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);
}
感謝
- neue cc - Unityでのボクシングの殺し方、或いはラムダ式における見えないnewの見極め方
- [雑記] デリゲートの内部 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
- Code Inspection: Convert lambda expression to method group | ReSharper
- Use a cached delegate for method group conversion · Issue #5835 · dotnet/roslyn
更新履歴
- 2022/03/04
- Issue へのリンク追記。
- 1のstaticメソッドのデリゲートは遅い件の補足追記。
関連記事
新着記事