From 3cc49044ec7a259291f4ccc058dc254982ec0a99 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 9 Aug 2026 20:01:46 +0200 Subject: [PATCH] Extend UseLambdaSyntax to emit statement lambdas Under UseLambdaSyntax, anonymous functions became lambdas only when an expression body was possible; statement-bodied ones kept C# 2 delegate syntax. Now every anonymous function whose parameter shape a lambda can express uses lambda syntax; delegate syntax remains for ref/out/in and params parameters and for pre-C# 3 language profiles. Two latent issues surfaced by the wider lambda coverage: DeclareVariables assumed an insertion point directly under a LambdaExpression is an expression body it must convert to a block, which block-bodied lambdas now violate; and anonymous methods declared without a parameter list carry compiler-generated parameter names like '' that are not valid identifiers, so the lambda's mandatory parameter list regenerates such names from the parameter type: (object obj, EventArgs e) => ... A side effect visible in fixtures: an explicit parameter list can make a delegate-creation cast redundant that bare 'delegate' syntax needed for overload resolution, e.g. new Thread((ThreadStart)delegate { }) becomes new Thread(() => { }). Assisted-by: Claude:claude-fable-5:Claude Code --- .../ILPrettyTestRunner.cs | 6 ++ .../ILPretty/AnonymousMethodEdgeCases.cs | 22 ++++++ .../ILPretty/AnonymousMethodEdgeCases.il | 79 +++++++++++++++++++ .../TestCases/ILPretty/Issue1038.cs | 2 +- .../TestCases/Pretty/Async.cs | 2 +- .../TestCases/Pretty/CustomTaskType.cs | 2 +- .../TestCases/Pretty/DeconstructionTests.cs | 2 +- .../TestCases/Pretty/DelegateConstruction.cs | 68 ++++++++++------ .../TestCases/Pretty/ExpressionTrees.cs | 6 +- .../TestCases/Pretty/FixProxyCalls.cs | 6 +- .../TestCases/Pretty/InitializerTests.cs | 6 +- .../TestCases/Pretty/Issue3439.cs | 4 +- .../TestCases/Pretty/Issue3751.cs | 2 +- .../TestCases/Pretty/LocalFunctions.cs | 26 +++--- .../TestCases/Pretty/OutVariables.cs | 2 +- .../TestCases/Pretty/PropertiesAndEvents.cs | 6 +- .../TestCases/Pretty/QualifierTests.cs | 2 +- .../TestCases/Pretty/TupleTests.cs | 6 +- ...eScalarReplacementOfAggregates.Expected.cs | 2 +- .../CSharp/ExpressionBuilder.cs | 5 +- .../CSharp/Transforms/DeclareVariables.cs | 15 +++- .../IL/Transforms/AssignVariableNames.cs | 8 ++ 22 files changed, 216 insertions(+), 63 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.il diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index f0d8afb67..117f54bef 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -51,6 +51,12 @@ namespace ICSharpCode.Decompiler.Tests } } + [Test] + public async Task AnonymousMethodEdgeCases() + { + await Run(); + } + [Test, Ignore("Need to decide how to represent virtual methods without 'newslot' flag")] public async Task Issue379() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.cs new file mode 100644 index 000000000..84d19f5f1 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.cs @@ -0,0 +1,22 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty +{ + public class AnonymousMethodEdgeCases + { + public Func AssignmentIsTheLambdaBody() + { + return (int x) => { + int num; + return num = x; + }; + } + + public Action UsedParameterWithInvalidName() + { + return (object value) => { + Console.WriteLine(value); + }; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.il new file mode 100644 index 000000000..e227e3be2 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/AnonymousMethodEdgeCases.il @@ -0,0 +1,79 @@ +// Hand-written input for two anonymous-method shapes that C# cannot express directly: +// an expression-bodied lambda whose body is the assignment declaring its own local, and +// a parameter whose metadata name is not a valid C# identifier but IS used in the body. +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) + .ver 4:0:0:0 +} +.assembly extern System.Core +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) + .ver 4:0:0:0 +} +.assembly AnonymousMethodEdgeCases +{ + .ver 1:0:0:0 +} +.module AnonymousMethodEdgeCases.dll +.imagebase 0x00400000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.AnonymousMethodEdgeCases + extends [mscorlib]System.Object +{ + .method public hidebysig specialname rtspecialname instance void .ctor () cil managed + { + .maxstack 8 + ldarg.0 + call instance void [mscorlib]System.Object::.ctor() + ret + } + + .method public hidebysig instance class [System.Core]System.Func`2 AssignmentIsTheLambdaBody () cil managed + { + .maxstack 8 + ldarg.0 + ldftn instance int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.AnonymousMethodEdgeCases::'b__1_0'(int32) + newobj instance void class [System.Core]System.Func`2::.ctor(object, native int) + ret + } + + // return num = x; -- the store's value is the return value, so the local has a store and + // no load, and the decompiled lambda body is the assignment itself. + .method private hidebysig instance int32 'b__1_0' (int32 x) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .maxstack 2 + .locals init ( + [0] int32 num + ) + ldarg.1 + dup + stloc.0 + ret + } + + .method public hidebysig instance class [System.Core]System.Action`1 UsedParameterWithInvalidName () cil managed + { + .maxstack 8 + ldarg.0 + ldftn instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.AnonymousMethodEdgeCases::'b__2_0'(object) + newobj instance void class [System.Core]System.Action`1::.ctor(object, native int) + ret + } + + // The parameter name '' is what C# emits for an anonymous method declared without a + // parameter list; used here, so the lambda's parameter list has to name it somehow. + .method private hidebysig instance void 'b__2_0' (object '') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .maxstack 8 + ldarg.1 + call void [mscorlib]System.Console::WriteLine(object) + ret + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1038.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1038.cs index 2ce15ac0f..83e679028 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1038.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1038.cs @@ -4,7 +4,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty { public class Issue1038 where TR : class, new() { - public event Action TestEvent = delegate { + public event Action TestEvent = (TK A_0, TR A_1) => { }; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs index 2c4e38545..0623e8f57 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs @@ -524,7 +524,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static Func> AsyncDelegate() { - return async delegate { + return async () => { await Task.Delay(10); return 2; }; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs index 409263b7a..9254eeafc 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs @@ -119,7 +119,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static Func> AsyncDelegate() { - return async delegate { + return async () => { await Task.Delay(10); return 2; }; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs index a07a220e3..7cbf45a4f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs @@ -990,7 +990,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { int a = 0; int b = 0; - await Task.Run(delegate { + await Task.Run(() => { (a, b) = GetTuple(); }); return a + b; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index dae612b3a..f1a8b708e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -58,14 +58,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public Action CaptureOfThis() { - return delegate { + return () => { CaptureOfThis(); }; } public Action CaptureOfThisAndParameter(int a) { - return delegate { + return () => { CaptureOfThisAndParameter(a); }; } @@ -76,7 +76,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { if (item > 0) { - return delegate { + return () => { CaptureOfThisAndParameter(item + a); }; } @@ -91,7 +91,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction int copyOfItem = item; if (item > 0) { - return delegate { + return () => { CaptureOfThisAndParameter(item + a + copyOfItem); }; } @@ -118,18 +118,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction private void Bug955() { - new Thread((ThreadStart)delegate { + new Thread(() => { }); } public void Bug951(int amount) { - DoAction(delegate { + DoAction(() => { if (amount < 0) { amount = 0; } - DoAction(delegate { + DoAction(() => { NoOp(amount); }); }); @@ -138,12 +138,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public void Bug951b() { int amount = Foo(); - DoAction(delegate { + DoAction(() => { if (amount < 0) { amount = 0; } - DoAction(delegate { + DoAction(() => { NoOp(amount); }); }); @@ -151,8 +151,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public void Bug951c(SomeData data) { - DoAction(delegate { - DoAction(delegate { + DoAction(() => { + DoAction(() => { DoSomething(data.Value); }); }); @@ -165,7 +165,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public Action Bug971_DelegateWithoutParameterList() { - return delegate { + return (object obj) => { }; } @@ -256,7 +256,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public Func GetFunc(Func f) { TCaptured captured = f(default(TNonCaptured)); - return delegate { + return () => { Console.WriteLine(captured.GetType().FullName); return captured; }; @@ -265,7 +265,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public Func GetFunc(Func f) { TCaptured captured = f(); - return delegate (TNonCaptured a, TNonCapturedMP d) { + return (TNonCaptured a, TNonCapturedMP d) => { Console.WriteLine(a.GetHashCode()); Console.WriteLine(captured.GetType().FullName); return captured; @@ -343,7 +343,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction for (int i = 0; i < 10; i++) { int counter; - list.Add(delegate (int x) { + list.Add((int x) => { counter = x; }); } @@ -356,7 +356,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction int counter; for (int i = 0; i < 10; i++) { - list.Add(delegate (int x) { + list.Add((int x) => { counter = x; }); } @@ -365,7 +365,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public static Action StaticAnonymousMethodNoClosure() { - return delegate { + return () => { Console.WriteLine(); }; } @@ -383,7 +383,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction int j; for (j = 0; j < 10; j++) { - list.Add(delegate (int k) { + list.Add((int k) => { for (int l = 0; l < j; l += k) { Console.WriteLine(); @@ -398,7 +398,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction List> list = new List>(); for (int i = 0; i < 10; i++) { - list.Add(delegate (int k) { + list.Add((int k) => { Console.WriteLine(k); }); } @@ -406,7 +406,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public static Action NameConflict3(int i) { - return delegate (int j) { + return (int j) => { for (int k = 0; k < j; k++) { Console.WriteLine(k); @@ -427,7 +427,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public static Func CapturedTypeParameter1(TNonCaptured a, Func f) { TCaptured captured = f(a); - return delegate { + return () => { Console.WriteLine(captured.GetType().FullName); return captured; }; @@ -436,7 +436,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public static Func CapturedTypeParameter2(TNonCaptured a, Func> f) { List captured = f(a); - return delegate { + return () => { Console.WriteLine(captured.GetType().FullName); return captured.FirstOrDefault(); }; @@ -624,14 +624,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { public void M() { - Run(delegate (object o) { + Run((object o) => { try { List list = o as List; - Action action = delegate { + Action action = () => { list.Select((int x) => x * 2); }; - Action action2 = delegate { + Action action2 = () => { list.Select((int x) => x * 2); }; Console.WriteLine(); @@ -655,6 +655,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction del(x); } + public void AnonymousMethodWithByRefParameters() + { + RefAction refAction = delegate (ref int reference) { + reference++; + }; + OutAction outAction = delegate (out int reference) { + reference = 1; + }; + int value = 0; + refAction(ref value); + outAction(out value); + Console.WriteLine(value); + } + public void Issue1572(DelegateConstruction.Dummy dum) { #if EXPECTED_OUTPUT @@ -675,4 +689,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction internal class MyAttribute : Attribute { } + + public delegate void OutAction(out int value); + + public delegate void RefAction(ref int value); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index 99acb2186..d2253581f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -818,15 +818,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Test>((int a) => a.ToString(), (int a) => a.ToString()); Test>((string a) => a.ToArray(), (string a) => a.ToArray()); Test>(() => 'a'.CompareTo('b') < 0, () => 'a'.CompareTo('b') < 0); - Test>(delegate (object lockObj, bool lockTaken) { + Test>((object lockObj, bool lockTaken) => { Monitor.Enter(lockObj, ref lockTaken); }, (object lockObj, bool lockTaken) => Monitor.Enter(lockObj, ref lockTaken)); Test>((string str, int num) => int.TryParse(str, out num), (string str, int num) => int.TryParse(str, out num)); Test>((string str, SimpleType t) => int.TryParse(str, out t.Field), (string str, SimpleType t) => int.TryParse(str, out t.Field)); - Test>(delegate (object o) { + Test>((object o) => { TestCall(o); }, (object o) => TestCall(o)); - Test>(delegate (object o) { + Test>((object o) => { TestCall(ref o); }, (object o) => TestCall(ref o)); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs index 2ad2039ca..a8bbaa5f4 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FixProxyCalls.cs @@ -96,7 +96,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty protected internal override void Test(string test) { - action = delegate (string a) { + action = (string a) => { base.Test(a); }; if (test.Equals(1)) @@ -119,7 +119,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty { public Action M(object state) { - return delegate (object x) { + return (object x) => { base.BaseCall(x, state, () => (object)null); }; } @@ -136,7 +136,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty { protected internal override void Test(int a) { - Action action = delegate { + Action action = () => { base.Test(a); }; if (a.Equals(1)) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index c59c83add..5afcdb5a2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -916,7 +916,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests public static void NotAnObjectInitializerWithEvent() { Data data = new Data(); - data.TestEvent += delegate { +#if NET50 + data.TestEvent += (object? obj, EventArgs e) => { +#else + data.TestEvent += (object obj, EventArgs e) => { +#endif Console.WriteLine(); }; X(Y(), data); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3439.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3439.cs index e6c3512e6..0fc84fdab 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3439.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3439.cs @@ -12,7 +12,7 @@ internal class VariableScopeTest private void Test(List list1) { - AddAction(delegate (List list2) { + AddAction((List list2) => { long num2 = 1L; foreach (string item in list1) { @@ -28,7 +28,7 @@ internal class VariableScopeTest { int preservedName = num; num++; - AddAction(item2, delegate (object x) { + AddAction(item2, (object x) => { SetValue(x, preservedName); }); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3751.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3751.cs index 536f859fe..eeb51fa1a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3751.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3751.cs @@ -13,7 +13,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public object Trigger() { - return Infer(delegate { + return Infer(() => { if (Cond) { Console.WriteLine(); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index e12420b35..838c529fd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -329,7 +329,7 @@ namespace LocalFunctions private int field; - private Lazy nonCapturinglocalFunctionInLambda = new Lazy(delegate { + private Lazy nonCapturinglocalFunctionInLambda = new Lazy(() => { return CreateValue(); #if CS80 @@ -342,7 +342,7 @@ namespace LocalFunctions } }); - private Lazy capturinglocalFunctionInLambda = new Lazy(delegate { + private Lazy capturinglocalFunctionInLambda = new Lazy(() => { int x = 42; return Do(); @@ -648,7 +648,7 @@ namespace LocalFunctions public static int LocalFunctionInLambda(IEnumerable xs) { - return xs.First(delegate (int x) { + return xs.First((int x) => { return Do(); bool Do() @@ -797,20 +797,20 @@ namespace LocalFunctions { t0 = 0; int t2 = t0; - return ((Func)delegate { + return ((Func)(() => { t0 = 0; t2 = 0; return ZZZ2(); - })(); + }))(); } int ZZZ2() { t0 = 0; int t3 = t0; #if !OPT - Func func = delegate { + Func func = () => { #else - return ((Func)delegate { + return ((Func)(() => { #endif t0 = 0; t3 = 0; @@ -819,7 +819,7 @@ namespace LocalFunctions }; return func(); #else - })(); + }))(); #endif } } @@ -840,20 +840,20 @@ namespace LocalFunctions { t0 = 0; int t2 = t0; - return ((Func)delegate { + return ((Func)(() => { t0 = 0; t2 = 0; return ZZZ2(); - })(); + }))(); } int ZZZ2() { t0 = 0; int t3 = t0; #if !OPT - Func func = delegate { + Func func = () => { #else - return ((Func)delegate { + return ((Func)(() => { #endif t0 = 0; t3 = 0; @@ -862,7 +862,7 @@ namespace LocalFunctions }; return func(); #else - })(); + }))(); #endif } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index a7cfc0d42..37a621916 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -37,7 +37,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty // to ensure that the value is initialized when the delegate is declared. if (d.Count > 2 && d.TryGetValue(42, out var value)) { - return delegate { + return () => { Console.WriteLine(value); }; } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs index 0206dade0..36bebd629 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PropertiesAndEvents.cs @@ -169,7 +169,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public event EventHandler AutomaticEvent; [field: NonSerialized] - public event EventHandler AutomaticEventWithInitializer = delegate { +#if NET50 + public event EventHandler AutomaticEventWithInitializer = (object? obj, EventArgs e) => { +#else + public event EventHandler AutomaticEventWithInitializer = (object obj, EventArgs e) => { +#endif }; #if ROSLYN diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QualifierTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QualifierTests.cs index 57d0dee5f..2f5eb6f5f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QualifierTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/QualifierTests.cs @@ -280,7 +280,7 @@ namespace ICSharpCode.Decompiler.Tests.Pretty { int fieldConflict = 5; Capturer(() => this.fieldConflict + fieldConflict); - Capturer(delegate { + Capturer(() => { int innerConflict = 5; return this.fieldConflict + fieldConflict + Capturer2(() => this.innerConflict + innerConflict + this.fieldConflict + fieldConflict); }); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs index 3599b1005..5c028cf00 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs @@ -100,14 +100,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public (int, int) AccessRest => (1, 2, 3, 4, 5, 6, 7, 8, 9).Rest; - public (string, object, Action) TargetTyping => (null, 1, delegate { + public (string, object, Action) TargetTyping => (null, 1, () => { #pragma warning disable format }); #pragma warning restore format - public object NotTargetTyping => ((string)null, (object)1, (Action)delegate { + public object NotTargetTyping => ((string)null, (object)1, (Action)(() => { #pragma warning disable format - }); + })); #pragma warning restore format public void UnnamedTupleOut(out (int, string, Action, dynamic) tuple) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.Expected.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.Expected.cs index b94c00d52..e022498b3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.Expected.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.Expected.cs @@ -207,7 +207,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly field1 = 1, field2 = "Hello World!" }; - Invoke(delegate { + Invoke(() => { displayClass.thisField = new Program(); }); Console.WriteLine("{0} {1}", this, displayClass.thisField); diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 6d1e9ef7a..9547f4f74 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2550,8 +2550,9 @@ namespace ICSharpCode.Decompiler.CSharp } else if (settings.UseLambdaSyntax && ame.Parameters.All(p => p.ParameterModifier == ReferenceKind.None && !p.IsParams)) { - // otherwise use lambda only if an expression lambda is possible - isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement); + // Lambdas cover statement bodies too; anonymous method syntax remains only for + // parameter shapes a lambda cannot express (ref/out/in and params modifiers). + isLambda = true; } // Remove the parameter list from an AnonymousMethodExpression if the parameters are not used in the method body var parameterReferencingIdentifiers = diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index fe3c2e70e..f6411f494 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -472,8 +472,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { // We can only insert variable declarations in blocks, but FindInsertionPoints() didn't // guarantee that it finds only blocks. - // Fix that up now. - while (!(v.InsertionPoint.nextNode.Parent is BlockStatement or LambdaExpression)) + // Fix that up now. A lambda is a valid stop only for its expression body (insertion + // will convert that body to a block); a point at a statement-bodied lambda's block + // itself must keep moving up into the enclosing scope. + while (!(v.InsertionPoint.nextNode.Parent is BlockStatement + || (v.InsertionPoint.nextNode.Parent is LambdaExpression && v.InsertionPoint.nextNode is Expression))) { if (v.InsertionPoint.nextNode.Parent is ForStatement f && v.InsertionPoint.nextNode == f.Initializers.FirstOrDefault() && IsMatchingAssignment(v, out _)) { @@ -593,6 +596,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (v.Type.IsByRefLike) return true; // by-ref-like variables always must be initialized at their declaration. + if (v.InsertionPoint.nextNode.Parent is LambdaExpression) + { + // The insertion point is an expression-bodied lambda's body. Combining would put a + // declaration statement in expression position ("x => int num = x;"); the separate + // declaration path turns the body into a block first, which stays valid C#. + return false; + } + if (v.InsertionPoint.nextNode.Slot?.Kind == Slots.ForInitializer) return true; // for-statement initializers always should combine declaration and initialization. diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 7aba498bc..06acdff08 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -234,6 +234,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (variables.TryGetValue(i, out var v)) variableMapping[v] = name; } + else if (!IsValidName(name)) + { + // Compiler-generated parameter names (e.g. "" on an anonymous method + // declared without a parameter list) are not valid C# identifiers. Skipping + // the reservation and the mapping leaves the parameter to AssignName, which + // generates a fresh name from the type for any name that fails IsValidName. + continue; + } string nameWithoutNumber = SplitName(name, out int newIndex); if (!parentScope.IsReservedVariableName(nameWithoutNumber, out _)) {