Browse Source

Typed table-leaf base + first 4 cor tables

Adds the generic MetadataTableTreeNode<TEntry> base — caches LoadTable's
result and runs it through MetadataTextWriter for the Phase 1 text dump.
Ports Module / TypeRef / TypeDef / AssemblyRef as the first concrete
leaves; the remaining 31 CorTables and 8 DebugTables continue to land
through the universal placeholder until subsequent commits replace
them. Also fixes MetadataTextWriter to convert enum values to their
underlying integer before applying digit-bearing format strings, since
Enum.ToString rejects "X8".

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
688564c94a
  1. 130
      ILSpy.Tests/Metadata/TypedMetadataTableTreeTests.cs
  2. 85
      ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs
  3. 79
      ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs
  4. 90
      ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
  5. 78
      ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs
  6. 39
      ILSpy/Metadata/MetadataTableTreeNode.cs
  7. 12
      ILSpy/Metadata/MetadataTablesTreeNode.cs
  8. 11
      ILSpy/Metadata/MetadataTextWriter.cs

130
ILSpy.Tests/Metadata/TypedMetadataTableTreeTests.cs

@ -0,0 +1,130 @@ @@ -0,0 +1,130 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Metadata;
[TestFixture]
public class TypedMetadataTableTreeTests
{
[AvaloniaTest]
public async Task AssemblyRefTableTreeNode_Decompiles_To_A_Row_Per_AssemblyReference()
{
// CoreLib's AssemblyRef table holds the assemblies it depends on (typically a
// handful: System.Runtime, System.Threading, …). Selecting the typed table node
// should render one row per reference with the reference's display name visible.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
// CoreLib has zero AssemblyRefs (it's the bottom of the dep chain), so target
// System.Linq instead — same approach as the broader assembly-tree tests.
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var assemblyRefNode = tablesNode.Children.OfType<AssemblyRefTableTreeNode>().Single();
assemblyRefNode.Kind.Should().Be(TableIndex.AssemblyRef);
assemblyRefNode.RowCount.Should().BeGreaterThan(0);
vm.AssemblyTreeModel.SelectNode(assemblyRefNode);
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync();
tab.Text.Should().Contain("AssemblyRef");
tab.Text.Should().Contain("Name");
tab.Text.Should().Contain("Version");
// CoreLib references almost always include System.Runtime or netstandard.
tab.Text.Should().MatchRegex(@"\b(System\.Runtime|netstandard|System\.Private\.CoreLib|System\.Threading)\b");
}
[AvaloniaTest]
public async Task TypeDefTableTreeNode_Decompiles_To_A_Row_Per_Type_Definition()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
typeDefNode.Kind.Should().Be(TableIndex.TypeDef);
// CoreLib has thousands of type definitions.
typeDefNode.RowCount.Should().BeGreaterThan(1000);
vm.AssemblyTreeModel.SelectNode(typeDefNode);
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync();
tab.Text.Should().Contain("TypeDef");
// Object is the very first entry every CLI assembly produces (after <Module>).
tab.Text.Should().Contain("Object");
}
[AvaloniaTest]
public async Task ModuleTableTreeNode_Decompiles_To_A_Single_Row_Carrying_The_Module_Name()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var moduleNode = tablesNode.Children.OfType<ModuleTableTreeNode>().Single();
moduleNode.RowCount.Should().Be(1, "Module is a one-row table by spec");
vm.AssemblyTreeModel.SelectNode(moduleNode);
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync();
tab.Text.Should().Contain("Module");
tab.Text.Should().Contain(".dll");
}
}

85
ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// 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;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
{
/// <summary>
/// View of the AssemblyRef table — every external assembly the module imports. Each row
/// carries the reference's name, version, culture, and the public-key-or-token blob
/// that pins it to a specific signing identity.
/// </summary>
public sealed class AssemblyRefTableTreeNode : MetadataTableTreeNode<AssemblyRefTableTreeNode.AssemblyRefEntry>
{
public AssemblyRefTableTreeNode(MetadataFile metadataFile)
: base(TableIndex.AssemblyRef, metadataFile)
{
}
protected override IReadOnlyList<AssemblyRefEntry> LoadTable()
{
var list = new List<AssemblyRefEntry>();
foreach (var row in metadataFile.Metadata.AssemblyReferences)
list.Add(new AssemblyRefEntry(metadataFile, row));
return list;
}
public sealed class AssemblyRefEntry
{
readonly MetadataFile metadataFile;
readonly AssemblyReferenceHandle handle;
readonly System.Reflection.Metadata.AssemblyReference assemblyRef;
public int RID => MetadataTokens.GetRowNumber(handle);
[ColumnInfo("X8")]
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 Version Version => assemblyRef.Version;
[ColumnInfo("X8")]
public AssemblyFlags Flags => assemblyRef.Flags;
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int PublicKeyOrToken => MetadataTokens.GetHeapOffset(assemblyRef.PublicKeyOrToken);
public string Name => metadataFile.Metadata.GetString(assemblyRef.Name);
public string Culture => metadataFile.Metadata.GetString(assemblyRef.Culture);
public AssemblyRefEntry(MetadataFile metadataFile, AssemblyReferenceHandle handle)
{
this.metadataFile = metadataFile;
this.handle = handle;
assemblyRef = metadataFile.Metadata.GetAssemblyReference(handle);
}
}
}
}

79
ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// 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;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
{
/// <summary>
/// View of the Module table — always exactly one row carrying the module name and the
/// MVID (the GUID identifying this module's identity). Reads the GUID heap for MVID and
/// the optional generation IDs (only present in EnC / hot-reload deltas).
/// </summary>
public sealed class ModuleTableTreeNode : MetadataTableTreeNode<ModuleTableTreeNode.ModuleEntry>
{
public ModuleTableTreeNode(MetadataFile metadataFile)
: base(TableIndex.Module, metadataFile)
{
}
protected override IReadOnlyList<ModuleEntry> LoadTable()
=> [new ModuleEntry(metadataFile, (ModuleDefinitionHandle)EntityHandle.ModuleDefinition)];
public sealed class ModuleEntry
{
readonly MetadataFile metadataFile;
readonly ModuleDefinitionHandle handle;
readonly ModuleDefinition moduleDef;
public int RID => MetadataTokens.GetRowNumber(handle);
[ColumnInfo("X8")]
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 Generation => moduleDef.Generation;
public string Name => metadataFile.Metadata.GetString(moduleDef.Name);
[ColumnInfo("X8")]
public int Mvid => MetadataTokens.GetHeapOffset(moduleDef.Mvid);
[ColumnInfo("X8")]
public int GenerationId => MetadataTokens.GetHeapOffset(moduleDef.GenerationId);
[ColumnInfo("X8")]
public int BaseGenerationId => MetadataTokens.GetHeapOffset(moduleDef.BaseGenerationId);
public ModuleEntry(MetadataFile metadataFile, ModuleDefinitionHandle handle)
{
this.metadataFile = metadataFile;
this.handle = handle;
moduleDef = metadataFile.Metadata.GetModuleDefinition();
}
}
}
}

90
ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs

@ -0,0 +1,90 @@ @@ -0,0 +1,90 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
{
/// <summary>
/// View of the TypeDef table — every type the module defines. The first row is the
/// pseudo-type &lt;Module&gt;, owning module-scoped fields and methods. Each row carries
/// attributes (visibility, layout, semantics), the optional base type, and pointers
/// into the FieldList / MethodList for the type's members.
/// </summary>
public sealed class TypeDefTableTreeNode : MetadataTableTreeNode<TypeDefTableTreeNode.TypeDefEntry>
{
public TypeDefTableTreeNode(MetadataFile metadataFile)
: base(TableIndex.TypeDef, metadataFile)
{
}
protected override IReadOnlyList<TypeDefEntry> LoadTable()
{
var list = new List<TypeDefEntry>();
foreach (var row in metadataFile.Metadata.TypeDefinitions)
list.Add(new TypeDefEntry(metadataFile, row));
return list;
}
public sealed class TypeDefEntry
{
readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle handle;
readonly TypeDefinition typeDef;
public int RID => MetadataTokens.GetRowNumber(handle);
[ColumnInfo("X8")]
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);
[ColumnInfo("X8")]
public TypeAttributes Attributes => typeDef.Attributes;
public string Name => metadataFile.Metadata.GetString(typeDef.Name);
public string Namespace => metadataFile.Metadata.GetString(typeDef.Namespace);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int BaseType => MetadataTokens.GetToken(typeDef.BaseType);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int FieldList => MetadataTokens.GetToken(typeDef.GetFields().FirstOrDefault());
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MethodList => MetadataTokens.GetToken(typeDef.GetMethods().FirstOrDefault());
public TypeDefEntry(MetadataFile metadataFile, TypeDefinitionHandle handle)
{
this.metadataFile = metadataFile;
this.handle = handle;
typeDef = metadataFile.Metadata.GetTypeDefinition(handle);
}
}
}
}

78
ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// 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;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
{
/// <summary>
/// View of the TypeRef table — every external type the module mentions. Each row points
/// at the resolution scope (an AssemblyRef, ModuleRef, or another TypeRef for nested
/// types) plus the namespace + name of the referenced type.
/// </summary>
public sealed class TypeRefTableTreeNode : MetadataTableTreeNode<TypeRefTableTreeNode.TypeRefEntry>
{
public TypeRefTableTreeNode(MetadataFile metadataFile)
: base(TableIndex.TypeRef, metadataFile)
{
}
protected override IReadOnlyList<TypeRefEntry> LoadTable()
{
var list = new List<TypeRefEntry>();
foreach (var row in metadataFile.Metadata.TypeReferences)
list.Add(new TypeRefEntry(metadataFile, row));
return list;
}
public sealed class TypeRefEntry
{
readonly MetadataFile metadataFile;
readonly TypeReferenceHandle handle;
readonly TypeReference typeRef;
public int RID => MetadataTokens.GetRowNumber(handle);
[ColumnInfo("X8")]
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);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ResolutionScope => MetadataTokens.GetToken(typeRef.ResolutionScope);
public string Name => metadataFile.Metadata.GetString(typeRef.Name);
public string Namespace => metadataFile.Metadata.GetString(typeRef.Namespace);
public TypeRefEntry(MetadataFile metadataFile, TypeReferenceHandle handle)
{
this.metadataFile = metadataFile;
this.handle = handle;
typeRef = metadataFile.Metadata.GetTypeReference(handle);
}
}
}
}

39
ILSpy/Metadata/MetadataTableTreeNode.cs

@ -17,10 +17,14 @@ @@ -17,10 +17,14 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
using ILSpy.TreeNodes;
namespace ILSpy.Metadata
@ -47,4 +51,39 @@ namespace ILSpy.Metadata @@ -47,4 +51,39 @@ namespace ILSpy.Metadata
public override object Icon => Images.Images.MetadataTable;
}
/// <summary>
/// Typed companion: holds the row materialiser and the shared text-render path. Rows are
/// loaded lazily and cached so repeated decompiles don't re-walk the metadata. Phase 2
/// adds a <c>CreateTab</c> override that hands the same <c>LoadTable()</c> result to a
/// DataGrid view; for now <see cref="ITextOutput"/> is the only renderer.
/// </summary>
public abstract class MetadataTableTreeNode<TEntry> : MetadataTableTreeNode
{
public const int PreviewLimit = 200;
IReadOnlyList<TEntry>? cached;
protected MetadataTableTreeNode(TableIndex kind, MetadataFile metadataFile)
: base(kind, metadataFile)
{
}
public override object Text => $"{(byte)Kind:X2} {Kind} ({RowCount})";
public override string ToString() => Kind.ToString();
protected abstract IReadOnlyList<TEntry> LoadTable();
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
var rows = cached ??= LoadTable();
var preview = rows.Count > PreviewLimit
? (IReadOnlyList<TEntry>)rows.Take(PreviewLimit).ToList()
: rows;
language.WriteCommentLine(output, $"{Kind} ({rows.Count} rows)");
MetadataTextWriter.WriteTable(language, output, preview);
if (rows.Count > preview.Count)
language.WriteCommentLine(output, $"... ({rows.Count - preview.Count} more rows; full view ships with the metadata DataGrid in a future release)");
}
}
}

12
ILSpy/Metadata/MetadataTablesTreeNode.cs

@ -24,6 +24,7 @@ using ICSharpCode.Decompiler; @@ -24,6 +24,7 @@ using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
namespace ILSpy.Metadata
@ -69,8 +70,15 @@ namespace ILSpy.Metadata @@ -69,8 +70,15 @@ namespace ILSpy.Metadata
}
}
// Phase 1e replaces this universal fallback with typed per-table subclasses.
// Typed leaves are added in passes (1e-i, 1e-ii, 1e-iii); any table not yet ported
// falls through to the universal placeholder so the navigation surface stays whole.
internal static MetadataTableTreeNode CreateTableTreeNode(TableIndex table, MetadataFile metadataFile)
=> new UnsupportedMetadataTableTreeNode(table, metadataFile);
=> table switch {
TableIndex.Module => new ModuleTableTreeNode(metadataFile),
TableIndex.TypeRef => new TypeRefTableTreeNode(metadataFile),
TableIndex.TypeDef => new TypeDefTableTreeNode(metadataFile),
TableIndex.AssemblyRef => new AssemblyRefTableTreeNode(metadataFile),
_ => new UnsupportedMetadataTableTreeNode(table, metadataFile),
};
}
}

11
ILSpy/Metadata/MetadataTextWriter.cs

@ -108,8 +108,15 @@ namespace ILSpy.Metadata @@ -108,8 +108,15 @@ namespace ILSpy.Metadata
return FormatHex(value, entry.Size * 2);
}
if (!string.IsNullOrEmpty(format) && value is IFormattable formattable)
return formattable.ToString(format, CultureInfo.InvariantCulture);
if (!string.IsNullOrEmpty(format))
{
// Enum.ToString rejects digit-bearing format strings ("X8" etc.); coerce to the
// underlying integer first so the column lines up with neighbouring hex cells.
if (value is Enum enumValue)
value = Convert.ChangeType(enumValue, enumValue.GetTypeCode(), CultureInfo.InvariantCulture)!;
if (value is IFormattable formattable)
return formattable.ToString(format, CultureInfo.InvariantCulture);
}
return value.ToString() ?? "";
}

Loading…
Cancel
Save