Skip to content

Commit 978c31c

Browse files
Add PropertyAndEventBackingFieldLookup to improve performance of MemberIsHidden.
1 parent 647475f commit 978c31c

File tree

5 files changed

+134
-69
lines changed

5 files changed

+134
-69
lines changed

ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
using System.Linq;
2424
using System.Reflection.Metadata;
2525
using System.Reflection.PortableExecutable;
26-
using System.Text.RegularExpressions;
2726
using System.Threading;
2827

2928
using ICSharpCode.Decompiler;
@@ -340,24 +339,13 @@ public static bool MemberIsHidden(MetadataFile module, EntityHandle member, Deco
340339
return true;
341340
if (settings.UsePrimaryConstructorSyntaxForNonRecordTypes && IsPrimaryConstructorParameterBackingField(field, metadata))
342341
return true;
343-
if (settings.AutomaticProperties && IsAutomaticPropertyBackingField(field, metadata, out var propertyName))
342+
if (settings.AutomaticProperties && module.PropertyAndEventBackingFieldLookup.IsPropertyBackingField(fieldHandle, out var propertyHandle))
344343
{
345-
if (!settings.GetterOnlyAutomaticProperties && IsGetterOnlyProperty(propertyName))
346-
return false;
347-
348-
bool IsGetterOnlyProperty(string propertyName)
344+
if (!settings.GetterOnlyAutomaticProperties)
349345
{
350-
var properties = metadata.GetTypeDefinition(field.GetDeclaringType()).GetProperties();
351-
foreach (var p in properties)
352-
{
353-
var pd = metadata.GetPropertyDefinition(p);
354-
string name = metadata.GetString(pd.Name);
355-
if (!metadata.StringComparer.Equals(pd.Name, propertyName))
356-
continue;
357-
PropertyAccessors accessors = pd.GetAccessors();
358-
return !accessors.Getter.IsNil && accessors.Setter.IsNil;
359-
}
360-
return false;
346+
PropertyAccessors accessors = metadata.GetPropertyDefinition(propertyHandle).GetAccessors();
347+
if (!accessors.Getter.IsNil && accessors.Setter.IsNil)
348+
return false;
361349
}
362350

363351
return true;
@@ -367,15 +355,9 @@ bool IsGetterOnlyProperty(string propertyName)
367355
return true;
368356
}
369357
// event-fields are not [CompilerGenerated]
370-
if (settings.AutomaticEvents)
358+
if (settings.AutomaticEvents && module.PropertyAndEventBackingFieldLookup.IsEventBackingField(fieldHandle, out _))
371359
{
372-
foreach (var ev in metadata.GetTypeDefinition(field.GetDeclaringType()).GetEvents())
373-
{
374-
var eventName = metadata.GetString(metadata.GetEventDefinition(ev).Name);
375-
var fieldName = metadata.GetString(field.Name);
376-
if (IsEventBackingFieldName(fieldName, eventName, out _))
377-
return true;
378-
}
360+
return true;
379361
}
380362
if (settings.ArrayInitializers && metadata.GetString(metadata.GetTypeDefinition(field.GetDeclaringType()).Name).StartsWith("<PrivateImplementationDetails>", StringComparison.Ordinal))
381363
{
@@ -404,27 +386,6 @@ static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader meta
404386
return metadata.GetString(field.Name).StartsWith("<>f__switch", StringComparison.Ordinal);
405387
}
406388

407-
static readonly Regex automaticPropertyBackingFieldRegex = new Regex(@"^<(.*)>k__BackingField$",
408-
RegexOptions.Compiled | RegexOptions.CultureInvariant);
409-
410-
static bool IsAutomaticPropertyBackingField(FieldDefinition field, MetadataReader metadata, out string propertyName)
411-
{
412-
propertyName = null;
413-
var name = metadata.GetString(field.Name);
414-
var m = automaticPropertyBackingFieldRegex.Match(name);
415-
if (m.Success)
416-
{
417-
propertyName = m.Groups[1].Value;
418-
return true;
419-
}
420-
if (name.StartsWith("_", StringComparison.Ordinal))
421-
{
422-
propertyName = name.Substring(1);
423-
return field.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.CompilerGenerated);
424-
}
425-
return false;
426-
}
427-
428389
internal static bool IsEventBackingFieldName(string fieldName, string eventName, out int suffixLength)
429390
{
430391
suffixLength = 0;

ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Collections.Generic;
2121
using System.Diagnostics;
2222
using System.Linq;
23+
using System.Reflection.Metadata;
2324

2425
using ICSharpCode.Decompiler.CSharp.Syntax;
2526
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
@@ -760,18 +761,19 @@ Identifier ReplaceEventFieldAnnotation(Identifier identifier)
760761
{
761762
var parent = identifier.Parent;
762763
var mrr = parent.Annotation<MemberResolveResult>();
763-
var field = mrr?.Member as IField;
764-
if (field == null || field.Accessibility != Accessibility.Private)
764+
if (mrr?.Member is not IField field || field.Accessibility != Accessibility.Private)
765765
return null;
766-
foreach (var ev in field.DeclaringType.GetEvents(null, GetMemberOptions.IgnoreInheritedMembers))
766+
var module = field.ParentModule as MetadataModule;
767+
if (module == null)
768+
return null;
769+
if (module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)field.MetadataToken, out var eventHandle))
767770
{
768-
if (CSharpDecompiler.IsEventBackingFieldName(field.Name, ev.Name, out int suffixLength) &&
769-
currentMethod.AccessorOwner != ev)
771+
var eventDef = module.ResolveEntity(eventHandle) as IEvent;
772+
if (eventDef != null && currentMethod.AccessorOwner != eventDef)
770773
{
771774
parent.RemoveAnnotations<MemberResolveResult>();
772-
parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, ev));
773-
if (suffixLength != 0)
774-
identifier.Name = identifier.Name.Substring(0, identifier.Name.Length - suffixLength);
775+
parent.AddAnnotation(new MemberResolveResult(mrr.TargetResult, eventDef));
776+
identifier.Name = eventDef.Name;
775777
return identifier;
776778
}
777779
}
@@ -912,20 +914,14 @@ bool CheckAutomaticEventMatch(Match m, CustomEventDeclaration ev, bool isAddAcce
912914
if (!m.Success)
913915
return false;
914916
Expression fieldExpression = m.Get<Expression>("field").Single();
915-
// field name must match event name
916-
switch (fieldExpression)
917-
{
918-
case IdentifierExpression identifier:
919-
if (!CSharpDecompiler.IsEventBackingFieldName(identifier.Identifier, ev.Name, out _))
920-
return false;
921-
break;
922-
case MemberReferenceExpression memberRef:
923-
if (!CSharpDecompiler.IsEventBackingFieldName(memberRef.MemberName, ev.Name, out _))
924-
return false;
925-
break;
926-
default:
927-
return false;
928-
}
917+
IField eventField = fieldExpression.GetSymbol() as IField;
918+
if (eventField == null)
919+
return false;
920+
var module = eventField.ParentModule as MetadataModule;
921+
if (module == null)
922+
return false;
923+
if (!module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)eventField.MetadataToken, out _))
924+
return false;
929925
var returnType = ev.ReturnType.GetResolveResult().Type;
930926
var eventType = m.Get<AstType>("type").Single().GetResolveResult().Type;
931927
// ignore tuple element names, dynamic and nullability
@@ -1042,9 +1038,11 @@ bool IsEventBackingField(FieldDeclaration fd)
10421038
return false;
10431039
if (fd.GetSymbol() is not IField f)
10441040
return false;
1041+
if (f.ParentModule is not MetadataModule module)
1042+
return false;
10451043
return f.Accessibility == Accessibility.Private
10461044
&& symbol.ReturnType.Equals(f.ReturnType)
1047-
&& CSharpDecompiler.IsEventBackingFieldName(f.Name, ev.Name, out _);
1045+
&& module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)f.MetadataToken, out _);
10481046
}
10491047
}
10501048
#endregion

ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
<Compile Include="IL\Transforms\RemoveUnconstrainedGenericReferenceTypeCheck.cs" />
111111
<Compile Include="Metadata\MetadataFile.cs" />
112112
<Compile Include="Metadata\ModuleReferenceMetadata.cs" />
113+
<Compile Include="Metadata\PropertyAndEventBackingFieldLookup.cs" />
113114
<Compile Include="NRTAttributes.cs" />
114115
<Compile Include="PartialTypeInfo.cs" />
115116
<Compile Include="CSharp\ProjectDecompiler\IProjectFileWriter.cs" />

ICSharpCode.Decompiler/Metadata/MetadataFile.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,18 @@ internal MethodSemanticsLookup MethodSemanticsLookup {
227227
}
228228
}
229229

230+
PropertyAndEventBackingFieldLookup? propertyAndEventBackingFieldLookup;
231+
232+
internal PropertyAndEventBackingFieldLookup PropertyAndEventBackingFieldLookup {
233+
get {
234+
var r = LazyInit.VolatileRead(ref propertyAndEventBackingFieldLookup);
235+
if (r != null)
236+
return r;
237+
else
238+
return LazyInit.GetOrSet(ref propertyAndEventBackingFieldLookup, new PropertyAndEventBackingFieldLookup(Metadata));
239+
}
240+
}
241+
230242
public MetadataFile(MetadataFileKind kind, string fileName, MetadataReaderProvider metadata, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default, int metadataOffset = 0, bool isEmbedded = false)
231243
{
232244
this.Kind = kind;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) 2025 Siegfried Pammer
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System.Collections.Generic;
20+
using System.Reflection.Metadata;
21+
22+
namespace ICSharpCode.Decompiler.Metadata
23+
{
24+
class PropertyAndEventBackingFieldLookup
25+
{
26+
private readonly MetadataReader metadata;
27+
private readonly Dictionary<FieldDefinitionHandle, PropertyDefinitionHandle> propertyLookup
28+
= new();
29+
private readonly Dictionary<FieldDefinitionHandle, EventDefinitionHandle> eventLookup
30+
= new();
31+
32+
public PropertyAndEventBackingFieldLookup(MetadataReader metadata)
33+
{
34+
this.metadata = metadata;
35+
36+
var nameToFieldMap = new Dictionary<string, FieldDefinitionHandle>();
37+
38+
foreach (var tdh in metadata.TypeDefinitions)
39+
{
40+
var type = metadata.GetTypeDefinition(tdh);
41+
42+
foreach (var fdh in type.GetFields())
43+
{
44+
var field = metadata.GetFieldDefinition(fdh);
45+
var name = metadata.GetString(field.Name);
46+
nameToFieldMap.Add(name, fdh);
47+
}
48+
49+
foreach (var pdh in type.GetProperties())
50+
{
51+
var property = metadata.GetPropertyDefinition(pdh);
52+
var name = metadata.GetString(property.Name);
53+
// default C# property backing field name is "<PropertyName>k__BackingField"
54+
if (nameToFieldMap.TryGetValue($"<{name}>k__BackingField", out var fieldHandle))
55+
{
56+
propertyLookup[fieldHandle] = pdh;
57+
}
58+
else if (nameToFieldMap.TryGetValue($"_{name}", out fieldHandle)
59+
&& fieldHandle.IsCompilerGenerated(metadata))
60+
{
61+
propertyLookup[fieldHandle] = pdh;
62+
}
63+
}
64+
65+
foreach (var edh in type.GetEvents())
66+
{
67+
var ev = metadata.GetEventDefinition(edh);
68+
var name = metadata.GetString(ev.Name);
69+
if (nameToFieldMap.TryGetValue(name, out var fieldHandle))
70+
{
71+
eventLookup[fieldHandle] = edh;
72+
}
73+
else if (nameToFieldMap.TryGetValue($"{name}Event", out fieldHandle))
74+
{
75+
eventLookup[fieldHandle] = edh;
76+
}
77+
}
78+
79+
nameToFieldMap.Clear();
80+
}
81+
}
82+
83+
public bool IsPropertyBackingField(FieldDefinitionHandle field, out PropertyDefinitionHandle handle)
84+
{
85+
return propertyLookup.TryGetValue(field, out handle);
86+
}
87+
88+
public bool IsEventBackingField(FieldDefinitionHandle field, out EventDefinitionHandle handle)
89+
{
90+
return eventLookup.TryGetValue(field, out handle);
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)