diff --git a/ILSpy/AppEnv/AppComposition.cs b/ILSpy/AppEnv/AppComposition.cs
index 116e073b7..cee63a84c 100644
--- a/ILSpy/AppEnv/AppComposition.cs
+++ b/ILSpy/AppEnv/AppComposition.cs
@@ -18,8 +18,10 @@
using System;
using System.Collections.Generic;
+using System.Composition;
using System.Composition.Hosting;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
@@ -45,6 +47,23 @@ namespace ILSpy.AppEnv
public static CompositionHost Current
=> current ?? throw new InvalidOperationException("Composition host is not yet initialized.");
+ ///
+ /// Resolves a single MEF export, or returns when the composition host
+ /// isn't initialized yet (design-time previewer, headless pre-boot) or no such export exists.
+ /// Unlike a blanket try/catch this does NOT swallow composition/activation errors: host
+ /// absence is checked explicitly and the built-in TryGetExport reports a missing export
+ /// as , so a genuinely broken export still surfaces its exception.
+ ///
+ public static T? TryGetExport() where T : class
+ => current is { } host && host.TryGetExport(out T? export) ? export : null;
+
+ ///
+ /// Resolves all MEF exports of , or an empty sequence when the host
+ /// isn't initialized yet. for the single-export case.
+ ///
+ public static IEnumerable TryGetExports() where T : class
+ => current?.GetExports() ?? Enumerable.Empty();
+
public static CompositionHost Initialize()
{
RegisterPluginResolver();
diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs
index 9bf5789de..c516de1a5 100644
--- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs
+++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs
@@ -183,7 +183,7 @@ namespace ILSpy.AssemblyTree
{
// Open the definition in a fresh carve-out tab instead of replacing the current view
// (e.g. "Decompile to new tab" on a symbol in the code).
- TryGetExport()?.OpenNodeInNewTab(resolved);
+ AppEnv.AppComposition.TryGetExport()?.OpenNodeInNewTab(resolved);
}
else
{
@@ -194,18 +194,11 @@ namespace ILSpy.AssemblyTree
// paints local-reference marks on every match once the new Text lands.
if (e.Source is null)
return;
- var dockWorkspace = TryGetExport();
+ var dockWorkspace = AppEnv.AppComposition.TryGetExport();
if (dockWorkspace?.ActiveDecompilerTab is { } decompTab)
decompTab.HighlightedReference = e.Source;
}
- static T? TryGetExport() where T : class
- {
- try
- { return AppEnv.AppComposition.Current.GetExport(); }
- catch { return null; }
- }
-
// True while the SelectedItem setter is replacing the collection via Clear()+Add();
// suppresses the selection-changed fan-out until the final state is in place so consumers
// never observe the transient empty/multi mid-replace.
@@ -455,7 +448,7 @@ namespace ILSpy.AssemblyTree
// and its instance IS the AboutCommand, so we can drive its welcome path directly.
void ShowAboutWelcomePage()
{
- var registry = TryGetExport();
+ var registry = AppEnv.AppComposition.TryGetExport();
var export = registry?.Commands
.FirstOrDefault(c => c.Metadata.Header == nameof(ICSharpCode.ILSpy.Properties.Resources._About))
?.CreateExport();
@@ -1050,7 +1043,7 @@ namespace ILSpy.AssemblyTree
var removed = new HashSet(oldItems.OfType());
if (removed.Count > 0)
{
- TryGetExport()?.PruneHistory(node =>
+ AppEnv.AppComposition.TryGetExport()?.PruneHistory(node =>
node.AncestorsAndSelf()
.OfType()
.Any(a => removed.Contains(a.LoadedAssembly)));
@@ -1092,7 +1085,7 @@ namespace ILSpy.AssemblyTree
/// (e.g. the ones just resolved).
///
public void RefreshDecompiledView()
- => TryGetExport()?.ForceRefreshActiveTab();
+ => AppEnv.AppComposition.TryGetExport()?.ForceRefreshActiveTab();
///
/// Resolves every assembly reference of each supplied assembly node through that
@@ -1154,7 +1147,7 @@ namespace ILSpy.AssemblyTree
// SelectedItem setter early-outs, and DockWorkspace.ShowSelectedNode's
// dedup short-circuits — leaving stale decompiled text. Force a fresh
// render. Mirrors WPF's RefreshDecompiledView() call.
- TryGetExport()?.ForceRefreshActiveTab();
+ AppEnv.AppComposition.TryGetExport()?.ForceRefreshActiveTab();
}
}
}
diff --git a/ILSpy/Commands/CompareContextMenuEntry.cs b/ILSpy/Commands/CompareContextMenuEntry.cs
index 857900e6e..4eedef208 100644
--- a/ILSpy/Commands/CompareContextMenuEntry.cs
+++ b/ILSpy/Commands/CompareContextMenuEntry.cs
@@ -51,7 +51,7 @@ namespace ILSpy.Commands
[AssemblyTreeNode left, AssemblyTreeNode right])
return;
- var dockWorkspace = TryGetExport();
+ var dockWorkspace = AppComposition.TryGetExport();
if (dockWorkspace == null)
return;
var pane = new CompareTabPageModel(left.LoadedAssembly, right.LoadedAssembly);
@@ -61,12 +61,5 @@ namespace ILSpy.Commands
// view is a pair, not a single-node decompile result.
dockWorkspace.OpenNewTab(pane);
}
-
- static T? TryGetExport() where T : class
- {
- try
- { return AppComposition.Current.GetExport(); }
- catch { return null; }
- }
}
}
diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs
index cf014d817..e96882a1b 100644
--- a/ILSpy/Docking/DockWorkspace.cs
+++ b/ILSpy/Docking/DockWorkspace.cs
@@ -673,16 +673,7 @@ namespace ILSpy.Docking
}
static IEnumerable TryGetProtocolHandlers()
- {
- try
- {
- return AppEnv.AppComposition.Current.GetExports();
- }
- catch
- {
- return System.Array.Empty();
- }
- }
+ => AppEnv.AppComposition.TryGetExports();
// Long-lived decompiler viewmodel — kept alive across metadata interludes so going
// back to text doesn't lose the previous decompile until a fresh one supersedes it.
@@ -1133,17 +1124,9 @@ namespace ILSpy.Docking
// code-behind subscribes to FocusRequested and posts the focus shift onto the
// dispatcher so the freshly-active pane has a frame to surface in the layout
// first. Resolving the pane through AppComposition (instead of injecting it)
- // keeps the dock-workspace decoupled from the search namespace.
- try
- {
- var search = AppEnv.AppComposition.Current.GetExport();
- search.RequestFocus();
- }
- catch
- {
- // Composition isn't available in design-time previews / minimal tests; the
- // activation alone is enough to be useful there.
- }
+ // keeps the dock-workspace decoupled from the search namespace. TryGetExport is
+ // null in design-time previews / minimal tests, where the activation alone suffices.
+ AppEnv.AppComposition.TryGetExport()?.RequestFocus();
}
public ContentTabPage OpenNewTab(ContentPageModel content, SharpTreeNode? sourceNode = null)
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index 4d66b321a..ec67bf510 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -627,19 +627,10 @@ namespace ILSpy.Languages
}
static IReadOnlyList TryDiscoverHandlers()
- {
- try
- {
- return AppEnv.AppComposition.Current.GetExports().ToArray();
- }
- catch
- {
- // Composition isn't available in tests that bypass the host (e.g. invoking
- // DecompileAsProject directly with a self-built LoadedAssembly). Fall back
- // to the raw-bytes behaviour from the base class.
- return System.Array.Empty();
- }
- }
+ // Composition isn't available in tests that bypass the host (e.g. invoking
+ // DecompileAsProject directly with a self-built LoadedAssembly); TryGetExports then
+ // yields nothing, falling back to the raw-bytes behaviour from the base class.
+ => AppEnv.AppComposition.TryGetExports().ToArray();
}
static List CollectFieldsAndCtors(ITypeDefinition type, bool isStatic)
@@ -820,17 +811,11 @@ namespace ILSpy.Languages
static bool HasReferenceErrors(MetadataFile module)
{
- try
- {
- var atm = AppEnv.AppComposition.Current.GetExport();
- var loadedAssembly = atm.AssemblyList?.GetAssemblies()
- .FirstOrDefault(la => la.GetMetadataFileOrNull() == module);
- return loadedAssembly?.LoadedAssemblyReferencesInfo.HasErrors == true;
- }
- catch
- {
- return false;
- }
+ // No AssemblyTreeModel without a composition host (tests / minimal hosts) -> no errors.
+ var atm = AppEnv.AppComposition.TryGetExport();
+ var loadedAssembly = atm?.AssemblyList?.GetAssemblies()
+ .FirstOrDefault(la => la.GetMetadataFileOrNull() == module);
+ return loadedAssembly?.LoadedAssemblyReferencesInfo.HasErrors == true;
}
}
}
diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs
index c41d18049..478377896 100644
--- a/ILSpy/Languages/ILAstLanguage.cs
+++ b/ILSpy/Languages/ILAstLanguage.cs
@@ -130,17 +130,6 @@ namespace ILSpy.Languages
this.transforms = CSharpDecompiler.GetILTransforms();
}
- // DockWorkspace is resolved lazily — taking it as an [ImportingConstructor] argument
- // creates a composition cycle (DockWorkspace already imports LanguageService, which
- // imports the registered Languages). Lazy lookup at decompile time breaks the cycle
- // and Costs only one MEF GetExport call per "Show Steps" button click.
- DockWorkspace? TryGetDockWorkspace()
- {
- try
- { return AppComposition.Current.GetExport(); }
- catch { return null; }
- }
-
public override void DecompileMethod(IMethod method, ITextOutput output, DecompilationOptions options)
{
base.DecompileMethod(method, output, options);
@@ -188,8 +177,11 @@ namespace ILSpy.Languages
OnStepperUpdated();
}
}
+ // DockWorkspace is resolved lazily here, not via [ImportingConstructor]: it imports
+ // LanguageService, which imports the registered Languages, so a constructor import would
+ // form a composition cycle. The lazy lookup costs one MEF resolve per "Show Steps" click.
(output as ISmartTextOutput)?.AddButton(Images.Images.ViewCode, "Show Steps", delegate {
- TryGetDockWorkspace()?.ShowToolPane(DebugStepsPaneModel.PaneContentId);
+ AppComposition.TryGetExport()?.ShowToolPane(DebugStepsPaneModel.PaneContentId);
});
output.WriteLine();
il.WriteTo(output, DebugStepsPaneModel.WritingOptions);
diff --git a/ILSpy/TextView/DecompilerTabPageModel.cs b/ILSpy/TextView/DecompilerTabPageModel.cs
index e8a4073b8..a9587de00 100644
--- a/ILSpy/TextView/DecompilerTabPageModel.cs
+++ b/ILSpy/TextView/DecompilerTabPageModel.cs
@@ -278,14 +278,7 @@ namespace ILSpy.TextView
// still construct cleanly.
TaskbarProgressService? taskbarProgress;
TaskbarProgressService? TaskbarProgress
- => taskbarProgress ??= TryGetExport();
-
- static T? TryGetExport() where T : class
- {
- try
- { return AppEnv.AppComposition.Current.GetExport(); }
- catch { return null; }
- }
+ => taskbarProgress ??= AppEnv.AppComposition.TryGetExport();
public DecompilerTabPageModel()
{
@@ -358,16 +351,12 @@ namespace ILSpy.TextView
{
if (CurrentNode is not { } node)
return;
- try
- {
- var languageService = AppEnv.AppComposition.Current.GetExport();
- var dockWorkspace = AppEnv.AppComposition.Current.GetExport();
- ILSpy.Commands.SaveCodeHelper.SaveNodeAsync(node, languageService, dockWorkspace).HandleExceptions();
- }
- catch
- {
- // Best-effort: no save path available (minimal host).
- }
+ // Best-effort: no save path available without a composition host (minimal/design-time host).
+ var languageService = AppEnv.AppComposition.TryGetExport();
+ var dockWorkspace = AppEnv.AppComposition.TryGetExport();
+ if (languageService is null || dockWorkspace is null)
+ return;
+ ILSpy.Commands.SaveCodeHelper.SaveNodeAsync(node, languageService, dockWorkspace).HandleExceptions();
}
// Fire-and-forget wrapper around DecompileAsync that observes the resulting Task.
@@ -653,35 +642,23 @@ namespace ILSpy.TextView
// Pulls the live DecompilerSettings via MEF and returns a clone for this run. Also
// bakes the active LanguageService.CurrentVersion into the clone — without this the
// toolbar's Language-Version dropdown writes-through to LanguageSettings but never
- // reaches the decompiler. MEF resolves are wrapped in try/catch so design-time /
- // minimal test hosts that bypass composition fall back to default settings rather
- // than throwing.
+ // reaches the decompiler. Resolves go through TryGetExport so design-time / minimal test
+ // hosts that bypass composition fall back to default settings rather than throwing.
static ICSharpCode.Decompiler.DecompilerSettings? TryGetLiveDecompilerSettings()
{
- try
- {
- var settingsService = AppEnv.AppComposition.Current.GetExport();
- var settings = settingsService.DecompilerSettings.Clone();
- ApplyDisplaySettings(settings, settingsService.DisplaySettings);
- try
- {
- var version = AppEnv.AppComposition.Current.GetExport().CurrentVersion;
- if (Enum.TryParse(version?.Version, out var languageVersion))
- settings.SetLanguageVersion(languageVersion);
- else
- settings.SetLanguageVersion(ICSharpCode.Decompiler.CSharp.LanguageVersion.Latest);
- }
- catch
- {
- // No LanguageService available (non-C# language or minimal host) — leave the
- // settings at whatever default the source DecompilerSettings carried.
- }
- return settings;
- }
- catch
- {
+ var settingsService = AppEnv.AppComposition.TryGetExport();
+ if (settingsService is null)
return null;
- }
+ var settings = settingsService.DecompilerSettings.Clone();
+ ApplyDisplaySettings(settings, settingsService.DisplaySettings);
+ // No LanguageService (non-C# language or minimal host) leaves version null, so the
+ // language version falls back to Latest below.
+ var version = AppEnv.AppComposition.TryGetExport()?.CurrentVersion;
+ if (Enum.TryParse(version?.Version, out var languageVersion))
+ settings.SetLanguageVersion(languageVersion);
+ else
+ settings.SetLanguageVersion(ICSharpCode.Decompiler.CSharp.LanguageVersion.Latest);
+ return settings;
}
///
diff --git a/ILSpy/Themes/DocumentSwitcherDropdownBehavior.cs b/ILSpy/Themes/DocumentSwitcherDropdownBehavior.cs
index 4b54a457c..237b1be90 100644
--- a/ILSpy/Themes/DocumentSwitcherDropdownBehavior.cs
+++ b/ILSpy/Themes/DocumentSwitcherDropdownBehavior.cs
@@ -69,7 +69,7 @@ namespace ILSpy.Themes
if (e.NewValue is not true)
return;
- var settings = TryGetSessionSettings();
+ var settings = AppComposition.TryGetExport()?.SessionSettings;
if (settings is not null)
{
System.ComponentModel.PropertyChangedEventHandler handler = (_, args) => {
@@ -138,7 +138,8 @@ namespace ILSpy.Themes
DockPanel.SetDock(button, Avalonia.Controls.Dock.Right);
dockPanel.Children.Insert(0, button);
- UpdateVisibility(strip, TryGetSessionSettings()?.MultiLineDocumentTabs ?? false);
+ UpdateVisibility(strip,
+ AppComposition.TryGetExport()?.SessionSettings?.MultiLineDocumentTabs ?? false);
}
static void PopulateMenu(DocumentTabStrip strip, ContextMenu menu)
@@ -166,14 +167,5 @@ namespace ILSpy.Themes
static Button? FindButton(DocumentTabStrip strip)
=> strip.GetVisualDescendants().OfType