diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 58546162b..67867375f 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -558,6 +558,11 @@ namespace ICSharpCode.Decompiler.CSharp
return new CallBuilder(this, typeSystem, settings).Build(inst, context.TypeHint);
}
+ protected internal override TranslatedExpression VisitCachedDelegate(CachedDelegate inst, TranslationContext context)
+ {
+ return Translate(inst.Argument, context.TypeHint).WithILInstruction(inst);
+ }
+
protected internal override TranslatedExpression VisitLdVirtDelegate(LdVirtDelegate inst, TranslationContext context)
{
return new CallBuilder(this, typeSystem, settings).Build(inst);
diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
index d682da2eb..58c5018b2 100644
--- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
@@ -27,6 +27,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
#nullable enable
@@ -270,6 +271,16 @@ namespace ICSharpCode.Decompiler.CSharp
else if (Expression is ObjectCreateExpression oce && conversion.Conversion.IsMethodGroupConversion
&& oce.Arguments.Count == 1 && expressionBuilder.settings.UseImplicitMethodGroupConversion)
{
+ // C# 11 caches static method groups. Keep explicit construction when the IL creates a fresh delegate.
+ if (conversion.Conversion.Method.IsStatic
+ && conversion.Conversion.Method is not LocalFunctionMethod { IsStaticLocalFunction: false }
+ && conversion.Conversion.Method.Parameters.Count == type.GetDelegateInvokeMethod()?.Parameters.Count
+ && expressionBuilder.settings.GetMinimumRequiredVersion() >= LanguageVersion.CSharp11_0
+ && expressionBuilder.currentFunction.Kind != ILFunctionKind.ExpressionTree
+ && !ILInstructions.Any(i => i is CachedDelegate))
+ {
+ return this;
+ }
return this.UnwrapChild(oce.Arguments.Single());
}
break;
diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
index 82d9560b1..e0bcba5c5 100644
--- a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
+++ b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
@@ -754,6 +754,15 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
HandleBinaryWithOptionalEvaluation(inst, inst.ValueInst, inst.FallbackInst);
}
+ protected internal override void VisitCachedDelegate(CachedDelegate inst)
+ {
+ DebugStartPoint(inst);
+ State cachedState = state.Clone();
+ inst.Argument.AcceptVisitor(this);
+ state.JoinWith(cachedState);
+ DebugEndPoint(inst);
+ }
+
protected internal override void VisitDynamicLogicOperatorInstruction(DynamicLogicOperatorInstruction inst)
{
HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right);
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index 18072653c..604c58c95 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -177,6 +177,8 @@ namespace ICSharpCode.Decompiler.IL
UnboxAny,
/// Creates an object instance and calls the constructor.
NewObj,
+ /// Reuses a cached delegate, evaluating the argument only when the cache is empty.
+ CachedDelegate,
/// Creates an array instance.
NewArr,
/// Returns the default value for a type.
@@ -4580,6 +4582,103 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
+{
+ /// Reuses a cached delegate, evaluating the argument only when the cache is empty.
+ public sealed partial class CachedDelegate : ILInstruction
+ {
+ public CachedDelegate(ILInstruction argument) : base(OpCode.CachedDelegate)
+ {
+ this.Argument = argument;
+ }
+ public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument");
+ ILInstruction argument = null!;
+ public ILInstruction Argument {
+ get { return this.argument; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.argument, value, 0);
+ }
+ }
+ protected sealed override int GetChildCount()
+ {
+ return 1;
+ }
+ protected sealed override ILInstruction GetChild(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ return this.argument;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ protected sealed override void SetChild(int index, ILInstruction value)
+ {
+ switch (index)
+ {
+ case 0:
+ this.Argument = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ protected sealed override SlotInfo GetChildSlot(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ return ArgumentSlot;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ public sealed override ILInstruction Clone()
+ {
+ var clone = (CachedDelegate)ShallowClone();
+ clone.Argument = this.argument.Clone();
+ return clone;
+ }
+ public override StackType ResultType => Argument.ResultType;
+ public override IType InferType(ICompilation compilation) => Argument.InferType(compilation);
+ protected override InstructionFlags ComputeFlags()
+ {
+ return argument.Flags | InstructionFlags.ControlFlow;
+ }
+ public override InstructionFlags DirectFlags {
+ get {
+ return InstructionFlags.ControlFlow;
+ }
+ }
+ protected override void WriteToCore(ITextOutput output, ILAstWritingOptions options)
+ {
+ WriteILRange(output, options);
+ output.Write(OpCode);
+ output.Write('(');
+ this.argument.WriteTo(output, options);
+ output.Write(')');
+ }
+ public override void AcceptVisitor(ILVisitor visitor)
+ {
+ visitor.VisitCachedDelegate(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor)
+ {
+ return visitor.VisitCachedDelegate(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor, C context)
+ {
+ return visitor.VisitCachedDelegate(this, context);
+ }
+ protected internal override bool PerformMatch(ILInstruction? other, ref Patterns.Match match)
+ {
+ var o = other as CachedDelegate;
+ return o != null && this.argument.PerformMatch(o.argument, ref match);
+ }
+ }
+}
+namespace ICSharpCode.Decompiler.IL
{
/// Creates an array instance.
public sealed partial class NewArr : ILInstruction
@@ -7412,6 +7511,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
+ protected internal virtual void VisitCachedDelegate(CachedDelegate inst)
+ {
+ Default(inst);
+ }
protected internal virtual void VisitNewArr(NewArr inst)
{
Default(inst);
@@ -7822,6 +7925,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
+ protected internal virtual T VisitCachedDelegate(CachedDelegate inst)
+ {
+ return Default(inst);
+ }
protected internal virtual T VisitNewArr(NewArr inst)
{
return Default(inst);
@@ -8232,6 +8339,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
+ protected internal virtual T VisitCachedDelegate(CachedDelegate inst, C context)
+ {
+ return Default(inst, context);
+ }
protected internal virtual T VisitNewArr(NewArr inst, C context)
{
return Default(inst, context);
@@ -8433,6 +8544,7 @@ namespace ICSharpCode.Decompiler.IL
"unbox",
"unbox.any",
"newobj",
+ "cached.delegate",
"newarr",
"default.value",
"throw",
@@ -9003,6 +9115,17 @@ namespace ICSharpCode.Decompiler.IL
type = default(IType);
return false;
}
+ public bool MatchCachedDelegate([NotNullWhen(true)] out ILInstruction? argument)
+ {
+ var inst = this as CachedDelegate;
+ if (inst != null)
+ {
+ argument = inst.Argument;
+ return true;
+ }
+ argument = default(ILInstruction);
+ return false;
+ }
public bool MatchNewArr([NotNullWhen(true)] out IType? type)
{
var inst = this as NewArr;
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index 8a20bff22..bfac4f2da 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -287,6 +287,9 @@
Unary, HasTypeOperand, MemoryAccess, MayThrow, ResultType("this.type")),
new OpCode("newobj", "Creates an object instance and calls the constructor.",
CustomClassName("NewObj"), Call, ResultType("Method.DeclaringType")),
+ new OpCode("cached.delegate", "Reuses a cached delegate, evaluating the argument only when the cache is empty.",
+ CustomChildren(new [] { new ChildInfo("argument") }), ControlFlow,
+ ResultType("Argument.InferType(compilation)", "Argument.ResultType")),
new OpCode("newarr", "Creates an array instance.",
CustomClassName("NewArr"), HasTypeOperand,
CustomChildren(new [] { new ArgumentInfo("indices") { IsCollection = true } }, true),
diff --git a/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs
index e67f31123..93941cdbe 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs
@@ -79,7 +79,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// }
/// ... one usage of CachedAnonMethodDelegate ...
/// =>
- /// ... one usage of DelegateConstruction ...
+ /// ... one usage of cached.delegate(DelegateConstruction) ...
///
bool CachedDelegateInitializationWithField(IfInstruction inst)
{
@@ -101,7 +101,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (usages.Length != 1)
return false;
context.Step("CachedDelegateInitializationWithField", inst);
- usages[0].ReplaceWith(value);
+ usages[0].ReplaceWith(new CachedDelegate(value));
context.EndStep(value);
return true;
}
@@ -111,7 +111,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc v(DelegateConstruction)
/// }
/// =>
- /// stloc v(DelegateConstruction)
+ /// stloc v(cached.delegate(DelegateConstruction))
///
bool CachedDelegateInitializationWithLocal(IfInstruction inst)
{
@@ -141,6 +141,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
context.Step("CachedDelegateInitializationWithLocal", inst);
((Block)otherStore.Parent).Instructions.Remove(otherStore);
+ ((StLoc)storeInst).Value = new CachedDelegate(value);
inst.ReplaceWith(storeInst);
context.EndStep(storeInst);
return true;
@@ -152,7 +153,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc s(stobj(ldsflda(CachedAnonMethodDelegate), DelegateConstruction))
/// }
/// =>
- /// stloc s(DelegateConstruction)
+ /// stloc s(cached.delegate(DelegateConstruction))
///
bool CachedDelegateInitializationRoslynInStaticWithLocal(IfInstruction inst)
{
@@ -174,7 +175,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationRoslynInStaticWithLocal", inst);
- storeBeforeIf.Value = stobj.Value;
+ storeBeforeIf.Value = new CachedDelegate(stobj.Value);
return true;
}
@@ -184,7 +185,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc s(stobj(ldflda(CachedAnonMethodDelegate), DelegateConstruction))
/// }
/// =>
- /// stloc s(DelegateConstruction)
+ /// stloc s(cached.delegate(DelegateConstruction))
///
bool CachedDelegateInitializationRoslynWithLocal(IfInstruction inst)
{
@@ -206,7 +207,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationRoslynWithLocal", inst);
- storeBeforeIf.Value = stobj.Value;
+ storeBeforeIf.Value = new CachedDelegate(stobj.Value);
return true;
}
@@ -217,7 +218,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc s(ldobj System.Action(ldsflda $I4-1))
/// }
/// =>
- /// stloc s(DelegateConstruction)
+ /// stloc s(cached.delegate(DelegateConstruction))
///
bool CachedDelegateInitializationVB(IfInstruction inst)
{
@@ -249,7 +250,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationVB", inst);
- var stloc = new StLoc(s, delegateConstruction);
+ var stloc = new StLoc(s, new CachedDelegate(delegateConstruction));
inst.ReplaceWith(stloc);
context.EndStep(stloc);
return true;
@@ -261,7 +262,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// }
/// leave IL_0005 (stsfld CachedAnonMethodDelegate(DelegateConstruction))
/// =>
- /// leave IL_0005 (DelegateConstruction)
+ /// leave IL_0005 (cached.delegate(DelegateConstruction))
///
bool CachedDelegateInitializationVBWithReturn(IfInstruction inst)
{
@@ -279,7 +280,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationVBWithReturn", inst);
- leaveAfterIf.Value = delegateConstruction;
+ leaveAfterIf.Value = new CachedDelegate(delegateConstruction);
return true;
}
@@ -290,7 +291,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc s(stobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure), DelegateConstruction))
/// }
/// =>
- /// stloc s(DelegateConstruction)
+ /// stloc s(cached.delegate(DelegateConstruction))
///
bool CachedDelegateInitializationVBWithClosure(IfInstruction inst)
{
@@ -322,7 +323,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationVBWithClosure", inst);
- var stloc = new StLoc(s, delegateConstruction);
+ var stloc = new StLoc(s, new CachedDelegate(delegateConstruction));
inst.ReplaceWith(stloc);
context.EndStep(stloc);
return true;
diff --git a/global.json b/global.json
index 115ca7301..58a3053ca 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "11.0.0",
+ "version": "11.0.100-preview.7.26381.103",
"rollForward": "major",
"allowPrerelease": true
},