Browse Source

Add abstraction layer for MEF

pull/3257/head
tom-englert 1 year ago committed by tom-englert
parent
commit
ea2fc92883
  1. 4
      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

4
Directory.Packages.props

@ -45,7 +45,9 @@ @@ -45,7 +45,9 @@
<PackageVersion Include="System.Reflection.Metadata" 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="TomsToolbox.Wpf.Styles" Version="2.17.3" />
<PackageVersion Include="TomsToolbox.Composition.Analyzer" Version="2.18.1" />
<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>
</Project>

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.ruleset

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

6
ILSpy.sln

@ -36,6 +36,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.ILSpyX", "ICSha @@ -36,6 +36,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.ILSpyX", "ICSha
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.BamlDecompiler", "ICSharpCode.BamlDecompiler\ICSharpCode.BamlDecompiler.csproj", "{81A30182-3378-4952-8880-F44822390040}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

18
ILSpy/App.xaml.cs

@ -42,6 +42,10 @@ using Microsoft.VisualStudio.Composition; @@ -42,6 +42,10 @@ using Microsoft.VisualStudio.Composition;
using TomsToolbox.Wpf.Styles;
using ICSharpCode.ILSpyX.TreeView;
using TomsToolbox.Composition;
using TomsToolbox.Wpf.Composition;
using System.ComponentModel.Composition.Hosting;
namespace ICSharpCode.ILSpy
{
/// <summary>
@ -52,8 +56,7 @@ namespace ICSharpCode.ILSpy @@ -52,8 +56,7 @@ namespace ICSharpCode.ILSpy
internal static CommandLineArguments CommandLineArguments;
internal static readonly IList<ExceptionData> StartupExceptions = new List<ExceptionData>();
public static ExportProvider ExportProvider { get; private set; }
public static IExportProviderFactory ExportProviderFactory { get; private set; }
public static IExportProvider ExportProvider { get; private set; }
internal class ExceptionData
{
@ -89,6 +92,12 @@ namespace ICSharpCode.ILSpy @@ -89,6 +92,12 @@ namespace ICSharpCode.ILSpy
}
TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException;
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);
EventManager.RegisterClassHandler(typeof(Window),
Hyperlink.RequestNavigateEvent,
@ -170,8 +179,9 @@ namespace ICSharpCode.ILSpy @@ -170,8 +179,9 @@ namespace ICSharpCode.ILSpy
// If/When any part needs to import ICompositionService, this will be needed:
// catalog.WithCompositionService();
var config = CompositionConfiguration.Create(catalog);
ExportProviderFactory = config.CreateExportProviderFactory();
ExportProvider = ExportProviderFactory.CreateExportProvider();
var exportProviderFactory = config.CreateExportProviderFactory();
ExportProvider = new ExportProviderAdapter(exportProviderFactory.CreateExportProvider());
// 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.
config.ThrowOnErrors();

12
ILSpy/ContextMenuEntry.cs

@ -31,6 +31,8 @@ using ICSharpCode.ILSpyX.Search; @@ -31,6 +31,8 @@ using ICSharpCode.ILSpyX.Search;
using ICSharpCode.ILSpy.Controls.TreeView;
using ICSharpCode.ILSpyX.TreeView;
using TomsToolbox.Composition;
namespace ICSharpCode.ILSpy
{
public interface IContextMenuEntry
@ -205,7 +207,7 @@ namespace ICSharpCode.ILSpy @@ -205,7 +207,7 @@ namespace ICSharpCode.ILSpy
readonly DecompilerTextView textView;
readonly ListBox listBox;
readonly DataGrid dataGrid;
readonly Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] entries;
readonly IExport<IContextMenuEntry, IContextMenuEntryMetadata>[] entries;
private ContextMenuProvider()
{
@ -288,8 +290,8 @@ namespace ICSharpCode.ILSpy @@ -288,8 +290,8 @@ namespace ICSharpCode.ILSpy
bool ShowContextMenu(TextViewContext context, out ContextMenu menu)
{
menu = new ContextMenu();
var menuGroups = new Dictionary<string, Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[]>();
Lazy<IContextMenuEntry, IContextMenuEntryMetadata>[] topLevelGroup = null;
var menuGroups = new Dictionary<string, IExport<IContextMenuEntry, IContextMenuEntryMetadata>[]>();
IExport<IContextMenuEntry, IContextMenuEntryMetadata>[] topLevelGroup = null;
foreach (var group in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.ParentMenuID))
{
if (group.Key == null)
@ -301,10 +303,10 @@ namespace ICSharpCode.ILSpy @@ -301,10 +303,10 @@ namespace ICSharpCode.ILSpy
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;
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))
{

88
ILSpy/ExportProviderAdapter.cs

@ -0,0 +1,88 @@ @@ -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 @@ @@ -50,6 +50,8 @@
<PackageReference Include="DataGridExtensions" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
<PackageReference Include="NaturalSort.Extension" />
<PackageReference Include="TomsToolbox.Composition.Analyzer" />
<PackageReference Include="TomsToolbox.Wpf.Composition" />
<PackageReference Include="TomsToolbox.Wpf.Styles" />
</ItemGroup>

4
ILSpy/Languages/Languages.cs

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

4
ILSpy/MainWindow.xaml.cs

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

10
ILSpy/Options/OptionsDialog.xaml.cs

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

Loading…
Cancel
Save