From 978c31ca5e0310e021b90f5364db7056fe04e403 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 15 Jun 2025 16:19:59 +0200 Subject: [PATCH] Add PropertyAndEventBackingFieldLookup to improve performance of MemberIsHidden. --- .../CSharp/CSharpDecompiler.cs | 53 ++--------- .../Transforms/PatternStatementTransform.cs | 44 +++++---- .../ICSharpCode.Decompiler.csproj | 1 + .../Metadata/MetadataFile.cs | 12 +++ .../PropertyAndEventBackingFieldLookup.cs | 93 +++++++++++++++++++ 5 files changed, 134 insertions(+), 69 deletions(-) create mode 100644 ICSharpCode.Decompiler/Metadata/PropertyAndEventBackingFieldLookup.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 07a4126de..5eba14b77 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -23,7 +23,6 @@ using System.IO; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; -using System.Text.RegularExpressions; using System.Threading; using ICSharpCode.Decompiler; @@ -340,24 +339,13 @@ namespace ICSharpCode.Decompiler.CSharp return true; if (settings.UsePrimaryConstructorSyntaxForNonRecordTypes && IsPrimaryConstructorParameterBackingField(field, metadata)) return true; - if (settings.AutomaticProperties && IsAutomaticPropertyBackingField(field, metadata, out var propertyName)) + if (settings.AutomaticProperties && module.PropertyAndEventBackingFieldLookup.IsPropertyBackingField(fieldHandle, out var propertyHandle)) { - if (!settings.GetterOnlyAutomaticProperties && IsGetterOnlyProperty(propertyName)) - return false; - - bool IsGetterOnlyProperty(string propertyName) + if (!settings.GetterOnlyAutomaticProperties) { - var properties = metadata.GetTypeDefinition(field.GetDeclaringType()).GetProperties(); - foreach (var p in properties) - { - var pd = metadata.GetPropertyDefinition(p); - string name = metadata.GetString(pd.Name); - if (!metadata.StringComparer.Equals(pd.Name, propertyName)) - continue; - PropertyAccessors accessors = pd.GetAccessors(); - return !accessors.Getter.IsNil && accessors.Setter.IsNil; - } - return false; + PropertyAccessors accessors = metadata.GetPropertyDefinition(propertyHandle).GetAccessors(); + if (!accessors.Getter.IsNil && accessors.Setter.IsNil) + return false; } return true; @@ -367,15 +355,9 @@ namespace ICSharpCode.Decompiler.CSharp return true; } // event-fields are not [CompilerGenerated] - if (settings.AutomaticEvents) + if (settings.AutomaticEvents && module.PropertyAndEventBackingFieldLookup.IsEventBackingField(fieldHandle, out _)) { - foreach (var ev in metadata.GetTypeDefinition(field.GetDeclaringType()).GetEvents()) - { - var eventName = metadata.GetString(metadata.GetEventDefinition(ev).Name); - var fieldName = metadata.GetString(field.Name); - if (IsEventBackingFieldName(fieldName, eventName, out _)) - return true; - } + return true; } if (settings.ArrayInitializers && metadata.GetString(metadata.GetTypeDefinition(field.GetDeclaringType()).Name).StartsWith("", StringComparison.Ordinal)) { @@ -404,27 +386,6 @@ namespace ICSharpCode.Decompiler.CSharp return metadata.GetString(field.Name).StartsWith("<>f__switch", StringComparison.Ordinal); } - static readonly Regex automaticPropertyBackingFieldRegex = new Regex(@"^<(.*)>k__BackingField$", - RegexOptions.Compiled | RegexOptions.CultureInvariant); - - static bool IsAutomaticPropertyBackingField(FieldDefinition field, MetadataReader metadata, out string propertyName) - { - propertyName = null; - var name = metadata.GetString(field.Name); - var m = automaticPropertyBackingFieldRegex.Match(name); - if (m.Success) - { - propertyName = m.Groups[1].Value; - return true; - } - if (name.StartsWith("_", StringComparison.Ordinal)) - { - propertyName = name.Substring(1); - return field.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.CompilerGenerated); - } - return false; - } - internal static bool IsEventBackingFieldName(string fieldName, string eventName, out int suffixLength) { suffixLength = 0; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index b0f2375a2..9ce9ea72b 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection.Metadata; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; @@ -760,18 +761,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { var parent = identifier.Parent; var mrr = parent.Annotation(); - var field = mrr?.Member as IField; - if (field == null || field.Accessibility != Accessibility.Private) + if (mrr?.Member is not IField field || field.Accessibility != Accessibility.Private) return null; - foreach (var ev in field.DeclaringType.GetEvents(null, GetMemberOptions.IgnoreInheritedMembers)) + var module = field.ParentModule as MetadataModule; + if (module == null) + return null; + if (module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)field.MetadataToken, out var eventHandle)) { - if (CSharpDecompiler.IsEventBackingFieldName(field.Name, ev.Name, out int suffixLength) && - currentMethod.AccessorOwner != ev) + var eventDef = module.ResolveEntity(eventHandle) as IEvent; + if (eventDef != null && currentMethod.AccessorOwner != eventDef) { parent.RemoveAnnotations(); - parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, ev)); - if (suffixLength != 0) - identifier.Name = identifier.Name.Substring(0, identifier.Name.Length - suffixLength); + parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, eventDef)); + identifier.Name = eventDef.Name; return identifier; } } @@ -912,20 +914,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (!m.Success) return false; Expression fieldExpression = m.Get("field").Single(); - // field name must match event name - switch (fieldExpression) - { - case IdentifierExpression identifier: - if (!CSharpDecompiler.IsEventBackingFieldName(identifier.Identifier, ev.Name, out _)) - return false; - break; - case MemberReferenceExpression memberRef: - if (!CSharpDecompiler.IsEventBackingFieldName(memberRef.MemberName, ev.Name, out _)) - return false; - break; - default: - return false; - } + IField eventField = fieldExpression.GetSymbol() as IField; + if (eventField == null) + return false; + var module = eventField.ParentModule as MetadataModule; + if (module == null) + return false; + if (!module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)eventField.MetadataToken, out _)) + return false; var returnType = ev.ReturnType.GetResolveResult().Type; var eventType = m.Get("type").Single().GetResolveResult().Type; // ignore tuple element names, dynamic and nullability @@ -1042,9 +1038,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return false; if (fd.GetSymbol() is not IField f) return false; + if (f.ParentModule is not MetadataModule module) + return false; return f.Accessibility == Accessibility.Private && symbol.ReturnType.Equals(f.ReturnType) - && CSharpDecompiler.IsEventBackingFieldName(f.Name, ev.Name, out _); + && module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)f.MetadataToken, out _); } } #endregion diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 23a46a09a..5e02ca705 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -110,6 +110,7 @@ + diff --git a/ICSharpCode.Decompiler/Metadata/MetadataFile.cs b/ICSharpCode.Decompiler/Metadata/MetadataFile.cs index b8c284027..410d1dc3e 100644 --- a/ICSharpCode.Decompiler/Metadata/MetadataFile.cs +++ b/ICSharpCode.Decompiler/Metadata/MetadataFile.cs @@ -227,6 +227,18 @@ namespace ICSharpCode.Decompiler.Metadata } } + PropertyAndEventBackingFieldLookup? propertyAndEventBackingFieldLookup; + + internal PropertyAndEventBackingFieldLookup PropertyAndEventBackingFieldLookup { + get { + var r = LazyInit.VolatileRead(ref propertyAndEventBackingFieldLookup); + if (r != null) + return r; + else + return LazyInit.GetOrSet(ref propertyAndEventBackingFieldLookup, new PropertyAndEventBackingFieldLookup(Metadata)); + } + } + public MetadataFile(MetadataFileKind kind, string fileName, MetadataReaderProvider metadata, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default, int metadataOffset = 0, bool isEmbedded = false) { this.Kind = kind; diff --git a/ICSharpCode.Decompiler/Metadata/PropertyAndEventBackingFieldLookup.cs b/ICSharpCode.Decompiler/Metadata/PropertyAndEventBackingFieldLookup.cs new file mode 100644 index 000000000..b28eb4264 --- /dev/null +++ b/ICSharpCode.Decompiler/Metadata/PropertyAndEventBackingFieldLookup.cs @@ -0,0 +1,93 @@ +// Copyright (c) 2025 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; + +namespace ICSharpCode.Decompiler.Metadata +{ + class PropertyAndEventBackingFieldLookup + { + private readonly MetadataReader metadata; + private readonly Dictionary propertyLookup + = new(); + private readonly Dictionary eventLookup + = new(); + + public PropertyAndEventBackingFieldLookup(MetadataReader metadata) + { + this.metadata = metadata; + + var nameToFieldMap = new Dictionary(); + + foreach (var tdh in metadata.TypeDefinitions) + { + var type = metadata.GetTypeDefinition(tdh); + + foreach (var fdh in type.GetFields()) + { + var field = metadata.GetFieldDefinition(fdh); + var name = metadata.GetString(field.Name); + nameToFieldMap.Add(name, fdh); + } + + foreach (var pdh in type.GetProperties()) + { + var property = metadata.GetPropertyDefinition(pdh); + var name = metadata.GetString(property.Name); + // default C# property backing field name is "k__BackingField" + if (nameToFieldMap.TryGetValue($"<{name}>k__BackingField", out var fieldHandle)) + { + propertyLookup[fieldHandle] = pdh; + } + else if (nameToFieldMap.TryGetValue($"_{name}", out fieldHandle) + && fieldHandle.IsCompilerGenerated(metadata)) + { + propertyLookup[fieldHandle] = pdh; + } + } + + foreach (var edh in type.GetEvents()) + { + var ev = metadata.GetEventDefinition(edh); + var name = metadata.GetString(ev.Name); + if (nameToFieldMap.TryGetValue(name, out var fieldHandle)) + { + eventLookup[fieldHandle] = edh; + } + else if (nameToFieldMap.TryGetValue($"{name}Event", out fieldHandle)) + { + eventLookup[fieldHandle] = edh; + } + } + + nameToFieldMap.Clear(); + } + } + + public bool IsPropertyBackingField(FieldDefinitionHandle field, out PropertyDefinitionHandle handle) + { + return propertyLookup.TryGetValue(field, out handle); + } + + public bool IsEventBackingField(FieldDefinitionHandle field, out EventDefinitionHandle handle) + { + return eventLookup.TryGetValue(field, out handle); + } + } +}