From b6cbcd648c0e3fe05c1ff6040a2b836d23e21d29 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 22 May 2026 19:14:48 +0200 Subject: [PATCH] Cache + filter Dock descendant scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The polymorphism modifier on the dock-layout JsonSerializerOptions ran a fresh AppDomain.GetAssemblies() + SelectMany(GetTypes()) + IsAssignableFrom walk for every polymorphic base type it built JsonTypeInfo for — IDockable, IDock, IRootDock, IDockWindow, IDocumentTemplate, IToolTemplate. Six independent passes over ~100 loaded assemblies, ~500 ms total on warm starts. Assisted-by: Claude:claude-opus-4-7:Claude Code --- ILSpy/Docking/ILSpyDockJson.cs | 40 +++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/ILSpy/Docking/ILSpyDockJson.cs b/ILSpy/Docking/ILSpyDockJson.cs index 188983cb4..88a6e8062 100644 --- a/ILSpy/Docking/ILSpyDockJson.cs +++ b/ILSpy/Docking/ILSpyDockJson.cs @@ -115,17 +115,47 @@ namespace ILSpy.Docking typeInfo.PolymorphismOptions = options; } - static IEnumerable GetConcreteDescendantsOf(Type baseType) - => AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.IsDynamic) + // One-shot scan of every loaded assembly for concrete, public, non-generic candidate + // types. The result feeds which previously + // re-scanned the entire AppDomain once per polymorphic base type — 6 base types, ~100 + // loaded assemblies, ~500 ms of repeated reflection. With the cache, the scan runs + // exactly once and each per-base lookup is a fast LINQ filter over the cached list. + static readonly Lazy> CandidateTypes = new(ScanCandidateTypes); + + static IReadOnlyList ScanCandidateTypes() + { + // Filter at the assembly level so we don't call GetTypes() on assemblies that + // couldn't possibly contain Dock-model types — System.*, Avalonia.*, Microsoft.*, + // AvaloniaEdit, ICSharpCode.Decompiler, etc. all qualify as "won't define an + // IDockable". Keep Dock.* (the dock library), the ILSpy assembly itself, + // and *.Plugin.dll (which can add custom panes). + return AppDomain.CurrentDomain.GetAssemblies() + .Where(IsRelevantAssembly) .SelectMany(SafeTypes) .Where(t => t != null && t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters - && (t.IsPublic || t.IsNestedPublic) - && baseType.IsAssignableFrom(t)) + && (t.IsPublic || t.IsNestedPublic)) .Cast() .Distinct() + .ToArray(); + } + + static bool IsRelevantAssembly(Assembly a) + { + if (a.IsDynamic) + return false; + var name = a.GetName().Name; + if (string.IsNullOrEmpty(name)) + return false; + return name.StartsWith("Dock.", StringComparison.Ordinal) + || name == "ILSpy" + || name.EndsWith(".Plugin", StringComparison.Ordinal); + } + + static IEnumerable GetConcreteDescendantsOf(Type baseType) + => CandidateTypes.Value + .Where(t => baseType.IsAssignableFrom(t)) .OrderBy(t => t.FullName, StringComparer.Ordinal); static IEnumerable SafeTypes(Assembly assembly)