From 464551ea5d1088ad0f7a6a232e58edb234332495 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 9 Jun 2026 20:31:24 +0200 Subject: [PATCH] Hoist metadata row-offset and heap-view boilerplate to shared bases The 23 typed table-row viewmodels each inlined the same MetadataOffset + GetTableMetadataOffset + GetTableRowSize * (rid - 1) arithmetic, and the four heap tree nodes each repeated an identical lazy-cache / "{name} Heap (count)" header / CreateTab shell that differed only in the heap name and how rows are walked. A single mistyped TableIndex or a divergent cache shell would have been invisible across that many copies. Add MetadataTableTreeNode.GetRowOffset (with a MetadataReader+offset overload for the one entry that lacks a MetadataFile) and a generic MetadataHeapTreeNode base carrying the cache, header, and tab, leaving subclasses to supply only the name and row materialiser. Assisted-by: Claude:claude-opus-4-8:Claude Code --- ILSpy/Metadata/BlobHeapTreeNode.cs | 26 ++-------- .../CorTables/AssemblyRefTableTreeNode.cs | 4 +- .../CorTables/AssemblyTableTreeNode.cs | 4 +- .../CorTables/ConstantTableTreeNode.cs | 4 +- .../CorTables/CustomAttributeTableTreeNode.cs | 4 +- .../CorTables/DeclSecurityTableTreeNode.cs | 4 +- .../Metadata/CorTables/EventTableTreeNode.cs | 4 +- .../CorTables/ExportedTypeTableTreeNode.cs | 4 +- .../Metadata/CorTables/FieldTableTreeNode.cs | 4 +- ILSpy/Metadata/CorTables/FileTableTreeNode.cs | 4 +- .../GenericParamConstraintTableTreeNode.cs | 4 +- .../CorTables/GenericParamTableTreeNode.cs | 4 +- .../ManifestResourceTableTreeNode.cs | 4 +- .../CorTables/MemberRefTableTreeNode.cs | 4 +- .../CorTables/MethodSpecTableTreeNode.cs | 4 +- .../Metadata/CorTables/MethodTableTreeNode.cs | 4 +- .../CorTables/ModuleRefTableTreeNode.cs | 4 +- .../Metadata/CorTables/ModuleTableTreeNode.cs | 4 +- .../Metadata/CorTables/ParamTableTreeNode.cs | 4 +- .../CorTables/PropertyTableTreeNode.cs | 4 +- .../CorTables/StandAloneSigTableTreeNode.cs | 4 +- .../CorTables/TypeDefTableTreeNode.cs | 4 +- .../CorTables/TypeRefTableTreeNode.cs | 4 +- .../CorTables/TypeSpecTableTreeNode.cs | 4 +- ILSpy/Metadata/GuidHeapTreeNode.cs | 28 ++--------- ILSpy/Metadata/MetadataHeapTreeNode.cs | 47 +++++++++++++++++++ ILSpy/Metadata/MetadataTableTreeNode.cs | 15 ++++++ ILSpy/Metadata/StringHeapTreeNode.cs | 26 ++-------- ILSpy/Metadata/UserStringHeapTreeNode.cs | 26 ++-------- 29 files changed, 97 insertions(+), 163 deletions(-) diff --git a/ILSpy/Metadata/BlobHeapTreeNode.cs b/ILSpy/Metadata/BlobHeapTreeNode.cs index 615280a5a..239428398 100644 --- a/ILSpy/Metadata/BlobHeapTreeNode.cs +++ b/ILSpy/Metadata/BlobHeapTreeNode.cs @@ -17,7 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; -using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -33,42 +32,23 @@ namespace ILSpy.Metadata /// dump; full structural decoding (e.g. parsed type signatures) is out of scope for /// the metadata viewer. /// - public sealed class BlobHeapTreeNode : MetadataHeapTreeNode + public sealed class BlobHeapTreeNode : MetadataHeapTreeNode { - List? entries; - public BlobHeapTreeNode(MetadataFile metadataFile) : base(HandleKind.Blob, metadataFile) { } - public override object Text => $"Blob Heap ({EnsureEntries().Count})"; - public override string ToString() => "Blob Heap"; - - public override ContentPageModel CreateTab() - { - var page = new MetadataTablePageModel { - Title = "Blob Heap", - Items = EnsureEntries(), - }; - MetadataColumnBuilder.Populate(page); - return page; - } + protected override string HeapName => "Blob Heap"; - List EnsureEntries() + protected override void LoadEntries(MetadataReader metadata, List list) { - if (entries is { } cached) - return cached; - var list = new List(); - var metadata = metadataFile.Metadata; var handle = MetadataTokens.BlobHandle(0); do { list.Add(new BlobHeapEntry(metadata, handle)); handle = metadata.GetNextHandle(handle); } while (!handle.IsNil); - entries = list; - return list; } public sealed class BlobHeapEntry diff --git a/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs index 96e204f1c..2886777ae 100644 --- a/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs @@ -58,9 +58,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.AssemblyRef) - + metadataFile.Metadata.GetTableRowSize(TableIndex.AssemblyRef) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.AssemblyRef, RID); public Version Version => assemblyRef.Version; diff --git a/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs b/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs index 4ce79cc51..87bcc0981 100644 --- a/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs @@ -55,9 +55,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(EntityHandle.AssemblyDefinition); [ColumnInfo("X8")] - public int Offset => metadataOffset - + metadata.GetTableMetadataOffset(TableIndex.Assembly) - + metadata.GetTableRowSize(TableIndex.Assembly) * (RID - 1); + public int Offset => GetRowOffset(metadata, metadataOffset, TableIndex.Assembly, RID); [ColumnInfo("X4")] public AssemblyHashAlgorithm HashAlgorithm => assembly.HashAlgorithm; diff --git a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs index 08f4a41bb..62aa4593b 100644 --- a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs @@ -56,9 +56,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.Constant) - + metadataFile.Metadata.GetTableRowSize(TableIndex.Constant) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.Constant, RID); [ColumnInfo("X8")] public ConstantTypeCode Type => constant.TypeCode; diff --git a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs index 13f4688e8..380d9da3b 100644 --- a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs @@ -57,9 +57,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.CustomAttribute) - + metadataFile.Metadata.GetTableRowSize(TableIndex.CustomAttribute) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.CustomAttribute, RID); [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(customAttr.Parent); diff --git a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs index 1734628a7..0d6cd0038 100644 --- a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs @@ -57,9 +57,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.DeclSecurity) - + metadataFile.Metadata.GetTableRowSize(TableIndex.DeclSecurity) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.DeclSecurity, RID); [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(declSecAttr.Parent); diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs index 606b2bff2..47fbb2bf7 100644 --- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs @@ -57,9 +57,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.Event) - + metadataFile.Metadata.GetTableRowSize(TableIndex.Event) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.Event, RID); [ColumnInfo("X8")] public EventAttributes Attributes => eventDef.Attributes; diff --git a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs index c49ae1971..c6340ae18 100644 --- a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs @@ -58,9 +58,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.ExportedType) - + metadataFile.Metadata.GetTableRowSize(TableIndex.ExportedType) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.ExportedType, RID); [ColumnInfo("X8")] public TypeAttributes Attributes => type.Attributes; diff --git a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs index aa93ef5e5..9a026792c 100644 --- a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs @@ -57,9 +57,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.Field) - + metadataFile.Metadata.GetTableRowSize(TableIndex.Field) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.Field, RID); [ColumnInfo("X8")] public FieldAttributes Attributes => fieldDef.Attributes; diff --git a/ILSpy/Metadata/CorTables/FileTableTreeNode.cs b/ILSpy/Metadata/CorTables/FileTableTreeNode.cs index 873707531..d4ec3225e 100644 --- a/ILSpy/Metadata/CorTables/FileTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FileTableTreeNode.cs @@ -55,9 +55,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.File) - + metadataFile.Metadata.GetTableRowSize(TableIndex.File) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.File, RID); [ColumnInfo("X8")] public int Attributes => assemblyFile.ContainsMetadata ? 1 : 0; diff --git a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs index 959cc56c9..8ce3e046c 100644 --- a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs @@ -59,9 +59,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.GenericParamConstraint) - + metadataFile.Metadata.GetTableRowSize(TableIndex.GenericParamConstraint) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.GenericParamConstraint, RID); [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Owner => MetadataTokens.GetToken(genericParamConstraint.Parameter); diff --git a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs index b9ac48bcb..e91406b74 100644 --- a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs @@ -57,9 +57,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.GenericParam) - + metadataFile.Metadata.GetTableRowSize(TableIndex.GenericParam) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.GenericParam, RID); public int Number => genericParam.Index; diff --git a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs index 5aa988e02..d3203709d 100644 --- a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs @@ -58,9 +58,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.ManifestResource) - + metadataFile.Metadata.GetTableRowSize(TableIndex.ManifestResource) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.ManifestResource, RID); [ColumnInfo("X8")] public ManifestResourceAttributes Attributes => manifestResource.Attributes; diff --git a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs index e016affbf..d4e87e33b 100644 --- a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs @@ -56,9 +56,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.MemberRef) - + metadataFile.Metadata.GetTableRowSize(TableIndex.MemberRef) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.MemberRef, RID); [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(memberRef.Parent); diff --git a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs index 5b67563ce..7ff16b327 100644 --- a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs @@ -58,9 +58,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.MethodSpec) - + metadataFile.Metadata.GetTableRowSize(TableIndex.MethodSpec) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.MethodSpec, RID); [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Method => MetadataTokens.GetToken(methodSpec.Method); diff --git a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs index 90879afc8..51501f448 100644 --- a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs @@ -58,9 +58,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.MethodDef) - + metadataFile.Metadata.GetTableRowSize(TableIndex.MethodDef) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.MethodDef, RID); [ColumnInfo("X8")] public MethodAttributes Attributes => methodDef.Attributes; diff --git a/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs index a9c5eb0b7..c41e51841 100644 --- a/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs @@ -55,9 +55,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.ModuleRef) - + metadataFile.Metadata.GetTableRowSize(TableIndex.ModuleRef) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.ModuleRef, RID); public string Name => metadataFile.Metadata.GetString(moduleRef.Name); diff --git a/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs b/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs index 36fe6da90..6ad8f2b5f 100644 --- a/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs @@ -51,9 +51,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.Module) - + metadataFile.Metadata.GetTableRowSize(TableIndex.Module) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.Module, RID); public int Generation => moduleDef.Generation; diff --git a/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs index 28c12edb3..377b7b972 100644 --- a/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs @@ -56,9 +56,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.Param) - + metadataFile.Metadata.GetTableRowSize(TableIndex.Param) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.Param, RID); [ColumnInfo("X8")] public ParameterAttributes Attributes => param.Attributes; diff --git a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs index b95803dc0..1b2edd6ef 100644 --- a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs @@ -57,9 +57,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.Property) - + metadataFile.Metadata.GetTableRowSize(TableIndex.Property) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.Property, RID); [ColumnInfo("X8")] public PropertyAttributes Attributes => propertyDef.Attributes; diff --git a/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs b/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs index ff894e679..468f5e9bb 100644 --- a/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs @@ -56,9 +56,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.StandAloneSig) - + metadataFile.Metadata.GetTableRowSize(TableIndex.StandAloneSig) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.StandAloneSig, RID); [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(standaloneSig.Signature); diff --git a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs index 0fdaa5523..1ebe044e8 100644 --- a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs @@ -61,9 +61,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.TypeDef) - + metadataFile.Metadata.GetTableRowSize(TableIndex.TypeDef) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.TypeDef, RID); [ColumnInfo("X8")] public TypeAttributes Attributes => typeDef.Attributes; diff --git a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs index 84dac67fe..203e7fc5f 100644 --- a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs @@ -56,9 +56,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.TypeRef) - + metadataFile.Metadata.GetTableRowSize(TableIndex.TypeRef) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.TypeRef, RID); [ColumnInfo("X8", Kind = ColumnKind.Token)] public int ResolutionScope => MetadataTokens.GetToken(typeRef.ResolutionScope); diff --git a/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs index 8d0d37ef4..c66a5b822 100644 --- a/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs @@ -58,9 +58,7 @@ namespace ILSpy.Metadata.CorTables public int Token => MetadataTokens.GetToken(handle); [ColumnInfo("X8")] - public int Offset => metadataFile.MetadataOffset - + metadataFile.Metadata.GetTableMetadataOffset(TableIndex.TypeSpec) - + metadataFile.Metadata.GetTableRowSize(TableIndex.TypeSpec) * (RID - 1); + public int Offset => GetRowOffset(metadataFile, TableIndex.TypeSpec, RID); [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Signature => MetadataTokens.GetHeapOffset(typeSpec.Signature); diff --git a/ILSpy/Metadata/GuidHeapTreeNode.cs b/ILSpy/Metadata/GuidHeapTreeNode.cs index cd2f4c0bf..88a115651 100644 --- a/ILSpy/Metadata/GuidHeapTreeNode.cs +++ b/ILSpy/Metadata/GuidHeapTreeNode.cs @@ -17,14 +17,11 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; -using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using ICSharpCode.Decompiler.Metadata; -using ILSpy.ViewModels; - namespace ILSpy.Metadata { /// @@ -32,39 +29,20 @@ namespace ILSpy.Metadata /// rows and a handful of other tables. Indexing starts at 1 because index 0 is reserved /// for "no GUID". /// - public sealed class GuidHeapTreeNode : MetadataHeapTreeNode + public sealed class GuidHeapTreeNode : MetadataHeapTreeNode { - List? entries; - public GuidHeapTreeNode(MetadataFile metadataFile) : base(HandleKind.Guid, metadataFile) { } - public override object Text => $"Guid Heap ({EnsureEntries().Count})"; - public override string ToString() => "Guid Heap"; - - public override ContentPageModel CreateTab() - { - var page = new MetadataTablePageModel { - Title = "Guid Heap", - Items = EnsureEntries(), - }; - MetadataColumnBuilder.Populate(page); - return page; - } + protected override string HeapName => "Guid Heap"; - List EnsureEntries() + protected override void LoadEntries(MetadataReader metadata, List list) { - if (entries is { } cached) - return cached; - var list = new List(); - var metadata = metadataFile.Metadata; int count = metadata.GetHeapSize(HeapIndex.Guid) >> 4; for (int i = 1; i <= count; i++) list.Add(new GuidHeapEntry(metadata, MetadataTokens.GuidHandle(i))); - entries = list; - return list; } public sealed class GuidHeapEntry diff --git a/ILSpy/Metadata/MetadataHeapTreeNode.cs b/ILSpy/Metadata/MetadataHeapTreeNode.cs index b5f4dad41..8cfc4321d 100644 --- a/ILSpy/Metadata/MetadataHeapTreeNode.cs +++ b/ILSpy/Metadata/MetadataHeapTreeNode.cs @@ -17,11 +17,13 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Reflection.Metadata; using ICSharpCode.Decompiler.Metadata; using ILSpy.TreeNodes; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -46,4 +48,49 @@ namespace ILSpy.Metadata public override object Icon => Images.Images.Heap; } + + /// + /// Typed companion that owns the lazy row cache, the "{name} Heap (count)" header, and the + /// DataGrid tab. Subclasses supply the heap's display name and materialise its rows, which is + /// all that differs between the four heaps. + /// + public abstract class MetadataHeapTreeNode : MetadataHeapTreeNode + where TEntry : class + { + List? entries; + + protected MetadataHeapTreeNode(HandleKind kind, MetadataFile metadataFile) + : base(kind, metadataFile) + { + } + + /// The heap's display name, e.g. "Blob Heap". + protected abstract string HeapName { get; } + + public override object Text => $"{HeapName} ({EnsureEntries().Count})"; + public override string ToString() => HeapName; + + public override ContentPageModel CreateTab() + { + var page = new MetadataTablePageModel { + Title = HeapName, + Items = EnsureEntries(), + }; + MetadataColumnBuilder.Populate(page); + return page; + } + + protected List EnsureEntries() + { + if (entries is { } cached) + return cached; + var list = new List(); + LoadEntries(metadataFile.Metadata, list); + entries = list; + return list; + } + + /// Materialises every heap row into . + protected abstract void LoadEntries(MetadataReader metadata, List list); + } } diff --git a/ILSpy/Metadata/MetadataTableTreeNode.cs b/ILSpy/Metadata/MetadataTableTreeNode.cs index 0449db696..ec883b547 100644 --- a/ILSpy/Metadata/MetadataTableTreeNode.cs +++ b/ILSpy/Metadata/MetadataTableTreeNode.cs @@ -53,6 +53,21 @@ namespace ILSpy.Metadata public override object Icon => Images.Images.MetadataTable; + /// + /// Byte offset of row (1-based) in , relative to + /// the start of the PE file. Every typed row viewmodel exposes this from its Offset + /// property so the hex view can jump to the raw bytes. + /// + protected static int GetRowOffset(MetadataFile file, TableIndex table, int rid) + => GetRowOffset(file.Metadata, file.MetadataOffset, table, rid); + + /// + /// Overload for the few entries that hold a raw + offset rather + /// than a . + /// + protected static int GetRowOffset(MetadataReader metadata, int metadataOffset, TableIndex table, int rid) + => metadataOffset + metadata.GetTableMetadataOffset(table) + metadata.GetTableRowSize(table) * (rid - 1); + /// /// Builds (and caches into ) a human-readable description of the /// entity a token column points at, e.g. (AssemblyReference) System.Runtime, .... diff --git a/ILSpy/Metadata/StringHeapTreeNode.cs b/ILSpy/Metadata/StringHeapTreeNode.cs index 34e2e655b..a2bdf6319 100644 --- a/ILSpy/Metadata/StringHeapTreeNode.cs +++ b/ILSpy/Metadata/StringHeapTreeNode.cs @@ -17,7 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; -using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -33,42 +32,23 @@ namespace ILSpy.Metadata /// demand via the first time a /// caller asks for the count or a preview row. /// - public sealed class StringHeapTreeNode : MetadataHeapTreeNode + public sealed class StringHeapTreeNode : MetadataHeapTreeNode { - List? entries; - public StringHeapTreeNode(MetadataFile metadataFile) : base(HandleKind.String, metadataFile) { } - public override object Text => $"String Heap ({EnsureEntries().Count})"; - public override string ToString() => "String Heap"; - - public override ContentPageModel CreateTab() - { - var page = new MetadataTablePageModel { - Title = "String Heap", - Items = EnsureEntries(), - }; - MetadataColumnBuilder.Populate(page); - return page; - } + protected override string HeapName => "String Heap"; - List EnsureEntries() + protected override void LoadEntries(MetadataReader metadata, List list) { - if (entries is { } cached) - return cached; - var list = new List(); - var metadata = metadataFile.Metadata; var handle = MetadataTokens.StringHandle(0); do { list.Add(new StringHeapEntry(metadata, handle)); handle = metadata.GetNextHandle(handle); } while (!handle.IsNil); - entries = list; - return list; } public sealed class StringHeapEntry diff --git a/ILSpy/Metadata/UserStringHeapTreeNode.cs b/ILSpy/Metadata/UserStringHeapTreeNode.cs index 093397ad3..97ddb8b57 100644 --- a/ILSpy/Metadata/UserStringHeapTreeNode.cs +++ b/ILSpy/Metadata/UserStringHeapTreeNode.cs @@ -17,7 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; -using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -32,42 +31,23 @@ namespace ILSpy.Metadata /// instruction. Distinct from #Strings (which carries identifier names); user strings /// are referenced by metadata tokens, not by member-name resolution. /// - public sealed class UserStringHeapTreeNode : MetadataHeapTreeNode + public sealed class UserStringHeapTreeNode : MetadataHeapTreeNode { - List? entries; - public UserStringHeapTreeNode(MetadataFile metadataFile) : base(HandleKind.UserString, metadataFile) { } - public override object Text => $"UserString Heap ({EnsureEntries().Count})"; - public override string ToString() => "UserString Heap"; - - public override ContentPageModel CreateTab() - { - var page = new MetadataTablePageModel { - Title = "UserString Heap", - Items = EnsureEntries(), - }; - MetadataColumnBuilder.Populate(page); - return page; - } + protected override string HeapName => "UserString Heap"; - List EnsureEntries() + protected override void LoadEntries(MetadataReader metadata, List list) { - if (entries is { } cached) - return cached; - var list = new List(); - var metadata = metadataFile.Metadata; var handle = MetadataTokens.UserStringHandle(0); do { list.Add(new UserStringHeapEntry(metadata, handle)); handle = metadata.GetNextHandle(handle); } while (!handle.IsNil); - entries = list; - return list; } public sealed class UserStringHeapEntry