Browse Source

Add abstraction layer for MEF

pull/3257/head
tom-englert 1 year ago committed by tom-englert
parent
commit
ea2fc92883
  1. 6
      Directory.Packages.props
  2. 4
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.ruleset
  3. 6
      ILSpy.sln
  4. 18
      ILSpy/App.xaml.cs
  5. 12
      ILSpy/ContextMenuEntry.cs
  6. 88
      ILSpy/ExportProviderAdapter.cs
  7. 2
      ILSpy/ILSpy.csproj
  8. 4
      ILSpy/Languages/Languages.cs
  9. 4
      ILSpy/MainWindow.xaml.cs
  10. 10
      ILSpy/Options/OptionsDialog.xaml.cs

6
Directory.Packages.props

@ -45,7 +45,9 @@
<PackageVersion Include="System.Reflection.Metadata" Version="8.0.0" /> <PackageVersion Include="System.Reflection.Metadata" Version="8.0.0" />
<PackageVersion Include="System.Resources.Extensions" Version="8.0.0" /> <PackageVersion Include="System.Resources.Extensions" Version="8.0.0" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" /> <PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.17.3" /> <PackageVersion Include="TomsToolbox.Composition.Analyzer" Version="2.18.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" /> <PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.18.1" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.18.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.ruleset

@ -79,4 +79,8 @@
<Rule Id="ProhibitedModifiersAnalyzer" Action="None" /> <Rule Id="ProhibitedModifiersAnalyzer" Action="None" />
<Rule Id="RECS0001" Action="Info" /> <Rule Id="RECS0001" Action="Info" />
</Rules> </Rules>
<Rules AnalyzerId="TomsToolbox.Composition.Analyzer" RuleNamespace="TomsToolbox.Composition.Analyzer">
<Rule Id="MEF001" Action="Info" />
<Rule Id="MEF006" Action="Hidden" />
</Rules>
</RuleSet> </RuleSet>

6
ILSpy.sln

@ -36,6 +36,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.ILSpyX", "ICSha
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.BamlDecompiler", "ICSharpCode.BamlDecompiler\ICSharpCode.BamlDecompiler.csproj", "{81A30182-3378-4952-8880-F44822390040}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.BamlDecompiler", "ICSharpCode.BamlDecompiler\ICSharpCode.BamlDecompiler.csproj", "{81A30182-3378-4952-8880-F44822390040}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D0858E90-DCD5-4FD9-AA53-7262FAB8BEDB}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

18
ILSpy/App.xaml.cs

@ -42,6 +42,10 @@ using Microsoft.VisualStudio.Composition;
using TomsToolbox.Wpf.Styles; using TomsToolbox.Wpf.Styles;
using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView;
using TomsToolbox.Composition;
using TomsToolbox.Wpf.Composition;
using System.ComponentModel.Composition.Hosting;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
/// <summary> /// <summary>
@ -52,8 +56,7 @@ namespace ICSharpCode.ILSpy
internal static CommandLineArguments CommandLineArguments; internal static CommandLineArguments CommandLineArguments;
internal static readonly IList<ExceptionData> StartupExceptions = new List<ExceptionData>(); internal static readonly IList<ExceptionData> StartupExceptions = new List<ExceptionData>();
public static ExportProvider ExportProvider { get; private set; } public static IExportProvider ExportProvider { get; private set; }
public static IExportProviderFactory ExportProviderFactory { get; private set; }
internal class ExceptionData internal class ExceptionData
{ {
@ -89,6 +92,12 @@ namespace ICSharpCode.ILSpy
} }
TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException; TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException;
InitializeMef().GetAwaiter().GetResult(); InitializeMef().GetAwaiter().GetResult();
// Register the export provider so that it can be accessed from WPF/XAML components.
ExportProviderLocator.Register(ExportProvider);
// Add data templates registered via MEF.
Resources.MergedDictionaries.Add(DataTemplateManager.CreateDynamicDataTemplates(ExportProvider));
Languages.Initialize(ExportProvider); Languages.Initialize(ExportProvider);
EventManager.RegisterClassHandler(typeof(Window), EventManager.RegisterClassHandler(typeof(Window),
Hyperlink.RequestNavigateEvent, Hyperlink.RequestNavigateEvent,
@ -170,8 +179,9 @@ namespace ICSharpCode.ILSpy
// If/When any part needs to import ICompositionService, this will be needed: // If/When any part needs to import ICompositionService, this will be needed:
// catalog.WithCompositionService(); // catalog.WithCompositionService();
var config = CompositionConfiguration.Create(catalog); var config = CompositionConfiguration.Create(catalog);
ExportProviderFactory = config.CreateExportProviderFactory(); var exportProviderFactory = config.CreateExportProviderFactory();
ExportProvider = ExportProviderFactory.CreateExportProvider(); ExportProvider = new ExportProviderAdapter(exportProviderFactory.CreateExportProvider());
// This throws exceptions for composition failures. Alternatively, the configuration's CompositionErrors property // This throws exceptions for composition failures. Alternatively, the configuration's CompositionErrors property
// could be used to log the errors directly. Used at the end so that it does not prevent the export provider setup. // could be used to log the errors directly. Used at the end so that it does not prevent the export provider setup.
config.ThrowOnErrors(); config.ThrowOnErrors();

12
ILSpy/ContextMenuEntry.cs

@ -31,6 +31,8 @@ using ICSharpCode.ILSpyX.Search;
using ICSharpCode.ILSpy.Controls.TreeView; using ICSharpCode.ILSpy.Controls.TreeView;
using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView;
using TomsToolbox.Composition;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
public interface IContextMenuEntry public interface IContextMenuEntry
@ -205,7 +207,7 @@ namespace ICSharpCode.ILSpy
readonly DecompilerTextView textView; readonly DecompilerTextView textView;
readonly ListBox listBox; readonly ListBox listBox;
readonly DataGrid dataGrid; readonly DataGrid dataGrid;
readonly Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] entries; readonly IExport<IContextMenuEntry, IContextMenuEntryMetadata>[] entries;
private ContextMenuProvider() private ContextMenuProvider()
{ {
@ -288,8 +290,8 @@ namespace ICSharpCode.ILSpy
bool ShowContextMenu(TextViewContext context, out ContextMenu menu) bool ShowContextMenu(TextViewContext context, out ContextMenu menu)
{ {
menu = new ContextMenu(); menu = new ContextMenu();
var menuGroups = new Dictionary<string, Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[]>(); var menuGroups = new Dictionary<string, IExport<IContextMenuEntry, IContextMenuEntryMetadata>[]>();
Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] topLevelGroup = null; IExport<IContextMenuEntry, IContextMenuEntryMetadata>[] topLevelGroup = null;
foreach (var group in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.ParentMenuID)) foreach (var group in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.ParentMenuID))
{ {
if (group.Key == null) if (group.Key == null)
@ -301,10 +303,10 @@ namespace ICSharpCode.ILSpy
menuGroups.Add(group.Key, group.ToArray()); menuGroups.Add(group.Key, group.ToArray());
} }
} }
BuildMenu(topLevelGroup ?? Array.Empty<Lazy<IContextMenuEntry, IContextMenuEntryMetadata>>(), menu.Items); BuildMenu(topLevelGroup ?? Array.Empty<IExport<IContextMenuEntry, IContextMenuEntryMetadata>>(), menu.Items);
return menu.Items.Count > 0; return menu.Items.Count > 0;
void BuildMenu(Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] menuGroup, ItemCollection parent) void BuildMenu(IExport<IContextMenuEntry, IContextMenuEntryMetadata>[] menuGroup, ItemCollection parent)
{ {
foreach (var category in menuGroup.GroupBy(c => c.Metadata.Category)) foreach (var category in menuGroup.GroupBy(c => c.Metadata.Category))
{ {

88
ILSpy/ExportProviderAdapter.cs

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.VisualStudio.Composition;
using TomsToolbox.Composition;
using TomsToolbox.Essentials;
namespace ICSharpCode.ILSpy;
#nullable enable
/// <summary>
/// Adapter for Microsoft.VisualStudio.Composition.<see cref="ExportProvider"/> to <see cref="IExportProvider"/>.
/// </summary>
public class ExportProviderAdapter : IExportProvider
{
private static readonly Type DefaultMetadataType = typeof(Dictionary<string, object>);
private readonly ExportProvider _exportProvider;
/// <summary>
/// Initializes a new instance of the <see cref="ExportProviderAdapter"/> class.
/// </summary>
/// <param name="exportProvider">The export provider.</param>
public ExportProviderAdapter(ExportProvider exportProvider)
{
_exportProvider = exportProvider;
}
event EventHandler<EventArgs>? IExportProvider.ExportsChanged { add { } remove { } }
T IExportProvider.GetExportedValue<T>(string? contractName) where T : class
{
return _exportProvider.GetExportedValue<T>(contractName) ?? throw new InvalidOperationException($"No export found for type {typeof(T).FullName} with contract '{contractName}'");
}
T? IExportProvider.GetExportedValueOrDefault<T>(string? contractName) where T : class
{
return _exportProvider.GetExportedValues<T>(contractName).SingleOrDefault();
}
#pragma warning disable CS8769 // Nullability of reference types in type of parameter doesn't match implemented member (possibly because of nullability attributes).
// can't apply NotNullWhen here, because ICSharpCode.Decompiler defines a duplicate attribute, and uses InternalsVisibleTo("ILSpy"), so this attribute is now ambiguous!
bool IExportProvider.TryGetExportedValue<T>(string? contractName, /*[NotNullWhen(true)]*/ out T? value) where T : class
#pragma warning restore CS8769 // Nullability of reference types in type of parameter doesn't match implemented member (possibly because of nullability attributes).
{
value = _exportProvider.GetExportedValues<T>(contractName).SingleOrDefault();
return !Equals(value, default(T));
}
IEnumerable<T> IExportProvider.GetExportedValues<T>(string? contractName) where T : class
{
return _exportProvider.GetExportedValues<T>(contractName);
}
IEnumerable<object> IExportProvider.GetExportedValues(Type contractType, string? contractName)
{
return _exportProvider
.GetExports(contractType, DefaultMetadataType, contractName)
.Select(item => item.Value)
.ExceptNullItems();
}
IEnumerable<IExport<object>> IExportProvider.GetExports(Type contractType, string? contractName)
{
return _exportProvider
.GetExports(contractType, DefaultMetadataType, contractName)
.Select(item => new ExportAdapter<object>(() => item.Value, new MetadataAdapter((IDictionary<string, object?>)item.Metadata)));
}
IEnumerable<IExport<T>> IExportProvider.GetExports<T>(string? contractName) where T : class
{
return _exportProvider
.GetExports(typeof(T), DefaultMetadataType, contractName)
.Select(item => new ExportAdapter<T>(() => (T?)item.Value, new MetadataAdapter((IDictionary<string, object?>)item.Metadata)));
}
IEnumerable<IExport<T, TMetadataView>> IExportProvider.GetExports<T, TMetadataView>(string? contractName) where T : class where TMetadataView : class
{
return _exportProvider
.GetExports<T, TMetadataView>(contractName)
.Select(item => new ExportAdapter<T, TMetadataView>(() => item.Value, item.Metadata));
}
}

2
ILSpy/ILSpy.csproj

@ -50,6 +50,8 @@
<PackageReference Include="DataGridExtensions" /> <PackageReference Include="DataGridExtensions" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
<PackageReference Include="NaturalSort.Extension" /> <PackageReference Include="NaturalSort.Extension" />
<PackageReference Include="TomsToolbox.Composition.Analyzer" />
<PackageReference Include="TomsToolbox.Wpf.Composition" />
<PackageReference Include="TomsToolbox.Wpf.Styles" /> <PackageReference Include="TomsToolbox.Wpf.Styles" />
</ItemGroup> </ItemGroup>

4
ILSpy/Languages/Languages.cs

@ -22,6 +22,8 @@ using System.Linq;
using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.Composition;
using TomsToolbox.Composition;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
public static class Languages public static class Languages
@ -39,7 +41,7 @@ namespace ICSharpCode.ILSpy
get { return allLanguages; } get { return allLanguages; }
} }
internal static void Initialize(ExportProvider ep) internal static void Initialize(IExportProvider ep)
{ {
List<Language> languages = new List<Language>(); List<Language> languages = new List<Language>();
languages.AddRange(ep.GetExportedValues<Language>()); languages.AddRange(ep.GetExportedValues<Language>());

4
ILSpy/MainWindow.xaml.cs

@ -62,6 +62,8 @@ using ICSharpCode.ILSpy.Controls.TreeView;
using Microsoft.Win32; using Microsoft.Win32;
using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView;
using TomsToolbox.Composition;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
/// <summary> /// <summary>
@ -245,7 +247,7 @@ namespace ICSharpCode.ILSpy
} }
Button MakeToolbarItem(Lazy<ICommand, IToolbarCommandMetadata> command) Button MakeToolbarItem(IExport<ICommand, IToolbarCommandMetadata> command)
{ {
return new Button { return new Button {
Style = ThemeManager.Current.CreateToolBarButtonStyle(), Style = ThemeManager.Current.CreateToolBarButtonStyle(),

10
ILSpy/Options/OptionsDialog.xaml.cs

@ -28,6 +28,8 @@ using System.Xml.Linq;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.Settings;
using TomsToolbox.Composition;
namespace ICSharpCode.ILSpy.Options namespace ICSharpCode.ILSpy.Options
{ {
public class TabItemViewModel public class TabItemViewModel
@ -47,16 +49,12 @@ namespace ICSharpCode.ILSpy.Options
/// </summary> /// </summary>
public partial class OptionsDialog : Window public partial class OptionsDialog : Window
{ {
readonly IExport<UIElement, IOptionsMetadata>[] optionPages;
readonly Lazy<UIElement, IOptionsMetadata>[] optionPages;
public OptionsDialog() public OptionsDialog()
{ {
InitializeComponent(); InitializeComponent();
// These used to have [ImportMany(..., RequiredCreationPolicy = CreationPolicy.NonShared)], so they use their own var ep = App.ExportProvider;
// ExportProvider instance.
// FIXME: Ideally, the export provider should be disposed when it's no longer needed.
var ep = App.ExportProviderFactory.CreateExportProvider();
this.optionPages = ep.GetExports<UIElement, IOptionsMetadata>("OptionPages").ToArray(); this.optionPages = ep.GetExports<UIElement, IOptionsMetadata>("OptionPages").ToArray();
ILSpySettings settings = ILSpySettings.Load(); ILSpySettings settings = ILSpySettings.Load();
foreach (var optionPage in optionPages.OrderBy(p => p.Metadata.Order)) foreach (var optionPage in optionPages.OrderBy(p => p.Metadata.Order))

Loading…
Cancel
Save