diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index cedf888944..f047b4fde1 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -327,6 +327,8 @@ public static bool MemberIsHidden(Metadata.PEFile module, EntityHandle member, D return true; if (settings.Dynamic && type.IsDelegate(metadata) && (name.StartsWith("<>A", StringComparison.Ordinal) || name.StartsWith("<>F", StringComparison.Ordinal))) return true; + if (settings.AnonymousMethods && type.IsAnonymousDelegate(metadata)) + return true; } if (settings.ArrayInitializers && settings.SwitchStatementOnString && name.StartsWith("", StringComparison.Ordinal)) return true; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 0a3dddb155..4881eb5956 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2409,10 +2409,10 @@ internal ExpressionWithResolveResult TranslateFunction(IType delegateType, ILFun // C# 10 lambdas can have attributes, but anonymous methods cannot isLambda = true; } - else if (settings.UseLambdaSyntax && ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None)) + else if (settings.UseLambdaSyntax) { // otherwise use lambda only if an expression lambda is possible - isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement); + isLambda = body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement; } // 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 b17960e36f..5581602d6d 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -590,6 +590,10 @@ void InsertVariableDeclarations(TransformContext context) { type = new SimpleType("var"); } + else if (context.Settings.NaturalTypeForLambdaAndMethodGroup && v.Type.ContainsAnonymousDelegate()) + { + type = new SimpleType("var"); + } else { type = context.TypeSystemAstBuilder.ConvertType(v.Type); @@ -622,6 +626,10 @@ void InsertVariableDeclarations(TransformContext context) { type = new SimpleType("var"); } + else if (context.Settings.NaturalTypeForLambdaAndMethodGroup && v.Type.ContainsAnonymousDelegate()) + { + type = new SimpleType("var"); + } else if (dirExpr.Annotation() != null) { type = new SimpleType("var"); diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 1032093b39..1f6bf368e1 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -149,6 +149,7 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) { fileScopedNamespaces = false; recordStructs = false; + naturalTypeForLambdaAndMethodGroup = false; } if (languageVersion < CSharp.LanguageVersion.CSharp11_0) { @@ -166,7 +167,7 @@ public CSharp.LanguageVersion GetMinimumRequiredVersion() { if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) return CSharp.LanguageVersion.CSharp11_0; - if (fileScopedNamespaces || recordStructs) + if (fileScopedNamespaces || recordStructs || naturalTypeForLambdaAndMethodGroup) return CSharp.LanguageVersion.CSharp10_0; if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns @@ -443,6 +444,24 @@ public bool FileScopedNamespaces { } } + bool naturalTypeForLambdaAndMethodGroup = true; + + /// + /// Use C# 10 natural types for delegates and lambdas. + /// + [Category("C# 10.0 / VS 2022")] + [Description("DecompilerSettings.NaturalTypeForLambdaAndMethodGroup")] + public bool NaturalTypeForLambdaAndMethodGroup { + get { return naturalTypeForLambdaAndMethodGroup; } + set { + if (naturalTypeForLambdaAndMethodGroup != value) + { + naturalTypeForLambdaAndMethodGroup = value; + OnPropertyChanged(); + } + } + } + bool parameterNullCheck = false; /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index d84091d9d2..19fc97ed74 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -594,7 +594,7 @@ static string GetNameByType(IType type) { return name; } - if (type.IsAnonymousType()) + if (type.IsAnonymousType() || type.IsAnonymousDelegate()) { name = "anon"; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 3a78c1ffce..684b0cf0f3 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -127,11 +127,11 @@ static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod static bool ContainsAnonymousType(IMethod method) { - if (method.ReturnType.ContainsAnonymousType()) + if (method.ReturnType.ContainsAnonymousTypeOrDelegate()) return true; foreach (var p in method.Parameters) { - if (p.Type.ContainsAnonymousType()) + if (p.Type.ContainsAnonymousTypeOrDelegate()) return true; } return false; diff --git a/ICSharpCode.Decompiler/NRExtensions.cs b/ICSharpCode.Decompiler/NRExtensions.cs index 7d98e27b44..50ff41e7ca 100644 --- a/ICSharpCode.Decompiler/NRExtensions.cs +++ b/ICSharpCode.Decompiler/NRExtensions.cs @@ -16,7 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.TypeSystem; @@ -66,6 +65,20 @@ public static bool IsAnonymousType(this IType type) return false; } + public static bool IsAnonymousDelegate(this IType type) + { + if (type == null) + return false; + if (string.IsNullOrEmpty(type.Namespace) && type.HasGeneratedName() + && type.Kind == TypeKind.Delegate + && (type.Name.Contains("AnonymousDelegate") || type.Name.StartsWith("<>F{"))) + { + ITypeDefinition td = type.GetDefinition(); + return td != null && td.IsCompilerGenerated(); + } + return false; + } + public static bool ContainsAnonymousType(this IType type) { var visitor = new ContainsAnonTypeVisitor(); @@ -73,14 +86,31 @@ public static bool ContainsAnonymousType(this IType type) return visitor.ContainsAnonType; } + public static bool ContainsAnonymousDelegate(this IType type) + { + var visitor = new ContainsAnonTypeVisitor(); + type.AcceptVisitor(visitor); + return visitor.ContainsAnonDelegate; + } + + public static bool ContainsAnonymousTypeOrDelegate(this IType type) + { + var visitor = new ContainsAnonTypeVisitor(); + type.AcceptVisitor(visitor); + return visitor.ContainsAnonType || visitor.ContainsAnonDelegate; + } + class ContainsAnonTypeVisitor : TypeVisitor { public bool ContainsAnonType; + public bool ContainsAnonDelegate; public override IType VisitOtherType(IType type) { if (IsAnonymousType(type)) ContainsAnonType = true; + if (IsAnonymousDelegate(type)) + ContainsAnonDelegate = true; return base.VisitOtherType(type); } @@ -88,6 +118,8 @@ public override IType VisitTypeDefinition(ITypeDefinition type) { if (IsAnonymousType(type)) ContainsAnonType = true; + if (IsAnonymousDelegate(type)) + ContainsAnonDelegate = true; return base.VisitTypeDefinition(type); } } diff --git a/ICSharpCode.Decompiler/SRMExtensions.cs b/ICSharpCode.Decompiler/SRMExtensions.cs index dff5dcef2d..275c1be579 100644 --- a/ICSharpCode.Decompiler/SRMExtensions.cs +++ b/ICSharpCode.Decompiler/SRMExtensions.cs @@ -464,6 +464,18 @@ public static bool IsAnonymousType(this TypeDefinition type, MetadataReader meta return false; } + public static bool IsAnonymousDelegate(this TypeDefinition type, MetadataReader metadata) + { + string name = metadata.GetString(type.Name); + if (type.Namespace.IsNil && type.HasGeneratedName(metadata) + && type.IsDelegate(metadata) + && (name.Contains("AnonymousDelegate") || name.StartsWith("<>F{"))) + { + return type.IsCompilerGenerated(metadata); + } + return false; + } + #region HasGeneratedName public static bool IsGeneratedName(this StringHandle handle, MetadataReader metadata) diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 1866a7b293..a8f94d9d01 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1109,6 +1109,15 @@ public static string DecompilerSettings_NativeIntegers { } } + /// + /// Looks up a localized string similar to Use natural delegate types for lambdas and method groups. + /// + public static string DecompilerSettings_NaturalTypeForLambdaAndMethodGroup { + get { + return ResourceManager.GetString("DecompilerSettings.NaturalTypeForLambdaAndMethodGroup", resourceCulture); + } + } + /// /// Looks up a localized string similar to Nullable reference types. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index cc08fca35e..81a81b9dec 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -393,6 +393,9 @@ Are you sure you want to continue? Use nint/nuint types + + Use natural delegate types for lambdas and method groups + Decompile ?. and ?[] operators