mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
C# 12 allows both on the explicitly typed parameter list of a lambda, and nowhere else: an anonymous method cannot declare either, and neither can a lambda whose parameter list is about to be dropped. Guarded by a setting so the output stays valid for earlier language versions. Only what the anonymous function's own metadata declares is written. A lambda may state a default the delegate does not have, a different one, or none where the delegate has one, and reflection over the lambda's method reports what the lambda declared - so filling either in from the delegate's Invoke would make the recompiled assembly describe itself differently from the original. Call sites are unaffected either way, because they bind against the delegate, which still declares both. Roslyn writes ParamArrayAttribute on the anonymous function's own method only from version 5 on; before that it stands on the delegate type alone, where it is not the lambda's to restate, so the fixture guards those cases on ROSLYN5. The correctness test reads the metadata back through reflection, which is the only place the difference is observable. Assisted-by: Claude:claude-opus-5[1m]:Claude Codepull/4057/head
8 changed files with 226 additions and 1 deletions
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
#pragma warning disable CS9099, CS9100
|
||||
using System; |
||||
using System.Reflection; |
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness |
||||
{ |
||||
class LambdaOptionalAndParamsParameters |
||||
{ |
||||
static void Main() |
||||
{ |
||||
#if CS120 && !NET40
|
||||
ReportMetadata(); |
||||
ReportCalls(); |
||||
#endif
|
||||
} |
||||
|
||||
#if CS120 && !NET40
|
||||
delegate int OptionalFunc(int x = 5); |
||||
|
||||
delegate int PlainFunc(int x); |
||||
|
||||
delegate void ParamsAction(params int[] xs); |
||||
|
||||
delegate void PlainAction(int[] xs); |
||||
|
||||
// A lambda's parameter list does not have to repeat what the delegate declares: it may
|
||||
// state a different default, one the delegate does not have, or none where the delegate
|
||||
// has one, and the same for the params modifier. Metadata records what the lambda itself
|
||||
// declared, and reflection reports that rather than the delegate's, so the decompiled
|
||||
// lambda has to carry the lambda's own list back.
|
||||
static void Report(string name, Delegate d) |
||||
{ |
||||
ParameterInfo p = d.Method.GetParameters()[0]; |
||||
Console.WriteLine("{0}: hasDefault={1} default={2} params={3}", |
||||
name, |
||||
p.HasDefaultValue, |
||||
p.HasDefaultValue ? p.DefaultValue : "none", |
||||
p.IsDefined(typeof(ParamArrayAttribute), inherit: false)); |
||||
} |
||||
|
||||
static void ReportMetadata() |
||||
{ |
||||
Report("DefaultOnlyInDelegate", (OptionalFunc)((int x) => x * 2)); |
||||
Report("DefaultOnlyInLambda", (PlainFunc)((int x = 3) => x * 2)); |
||||
Report("DefaultDiffersFromDelegate", (OptionalFunc)((int x = 7) => x * 2)); |
||||
Report("DefaultAgreesWithDelegate", (OptionalFunc)((int x = 5) => x * 2)); |
||||
Report("ParamsOnlyInDelegate", (ParamsAction)((int[] xs) => Console.WriteLine(xs.Length))); |
||||
Report("ParamsOnlyInLambda", (PlainAction)((params int[] xs) => Console.WriteLine(xs.Length))); |
||||
} |
||||
|
||||
// The value a caller gets for an omitted argument comes from the delegate, never from the
|
||||
// lambda, so these stay the same whatever the lambda declared.
|
||||
static void ReportCalls() |
||||
{ |
||||
OptionalFunc noDefault = (int x) => x * 2; |
||||
OptionalFunc otherDefault = (int x = 7) => x * 2; |
||||
Console.WriteLine(noDefault()); |
||||
Console.WriteLine(otherDefault()); |
||||
Console.WriteLine(otherDefault(1)); |
||||
|
||||
ParamsAction expandedByDelegate = (int[] xs) => Console.WriteLine(xs.Length); |
||||
expandedByDelegate(1, 2, 3); |
||||
PlainAction notExpanded = (params int[] xs) => Console.WriteLine(xs.Length); |
||||
notExpanded(new int[2]); |
||||
} |
||||
#endif
|
||||
} |
||||
} |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
#pragma warning disable CS9099, CS9100
|
||||
using System; |
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty |
||||
{ |
||||
internal class LambdaOptionalAndParamsParameters |
||||
{ |
||||
public delegate void ParamsAction(params int[] xs); |
||||
|
||||
public delegate int OptionalFunc(int x = 5); |
||||
|
||||
public delegate int PlainFunc(int x); |
||||
|
||||
// A lambda states 'params' and defaults on its own account: what the delegate declares
|
||||
// is the delegate's, and reflection over the lambda's method reports only the lambda's.
|
||||
// Roslyn records the modifier on the anonymous function's method from version 5 on, so
|
||||
// the cases that need it back are guarded on ROSLYN5.
|
||||
|
||||
private int total; |
||||
|
||||
#if ROSLYN5
|
||||
public ParamsAction ParamsStatementBody() |
||||
{ |
||||
return (params int[] xs) => { |
||||
total += xs.Length; |
||||
Console.WriteLine(xs.Length); |
||||
}; |
||||
} |
||||
#endif
|
||||
|
||||
#if ROSLYN5
|
||||
public ParamsAction ParamsSingleStatementBody() |
||||
{ |
||||
return (params int[] xs) => { |
||||
Console.WriteLine(xs.Length); |
||||
}; |
||||
} |
||||
#endif
|
||||
|
||||
public OptionalFunc OptionalStatementBody() |
||||
{ |
||||
return (int x = 5) => { |
||||
total += x; |
||||
return x * 2; |
||||
}; |
||||
} |
||||
|
||||
public OptionalFunc OptionalExpressionBody() |
||||
{ |
||||
return (int x = 5) => x * 2; |
||||
} |
||||
|
||||
public ParamsAction ParamsWithoutParameterList() |
||||
{ |
||||
return delegate { |
||||
}; |
||||
} |
||||
|
||||
public OptionalFunc OptionalWithoutParameterList() |
||||
{ |
||||
return delegate { |
||||
return 1; |
||||
}; |
||||
} |
||||
|
||||
// The lambda's own defaults and params modifier can differ from the delegate's; the metadata
|
||||
// records the lambda's, so that is what must round-trip.
|
||||
public OptionalFunc OptionalDifferentDefault() |
||||
{ |
||||
return (int x = 7) => x * 2; |
||||
} |
||||
|
||||
public OptionalFunc OptionalOnlyInDelegate() |
||||
{ |
||||
return (int x) => x * 2; |
||||
} |
||||
|
||||
public PlainFunc OptionalOnlyInLambda() |
||||
{ |
||||
return (int x = 3) => x * 2; |
||||
} |
||||
|
||||
public ParamsAction ParamsOnlyInDelegate() |
||||
{ |
||||
return (int[] xs) => { |
||||
Console.WriteLine(xs.Length); |
||||
}; |
||||
} |
||||
|
||||
#if ROSLYN5
|
||||
public Action<int[]> ParamsOnlyInLambda() |
||||
{ |
||||
return (params int[] xs) => { |
||||
Console.WriteLine(xs.Length); |
||||
}; |
||||
} |
||||
#endif
|
||||
} |
||||
} |
||||
Loading…
Reference in new issue