Reputation: 3446
I've shared MEF exports that implement IAsyncDisposable
.
If an export in MEF implements IDisposable
it will be disposed when the composition container (or maybe the catalog) is disposed.
IAsyncDisposable
is not recognized from MEF. Is there any solution to that problem?
If not:
If the application will be shut down I try to iterate over all already created exports that implement IAsyncDisposable
but there seems to be no possibility to do that.
Let's assume that I've a typed method (maybe created via reflection) I'm able to call CompositionContainer.GetExport<T>()
that returns a Lazy<T>
. The problem is that IsValueCreated
is false
- even if that import has a running shared instance.
Is there any way to iterate over all exports that have been already instantiated?
This code shows that the Lazy<T>
-instance does not contain the already known exported value:
public class Program
{
public static void Main()
{
using var catalog = new AssemblyCatalog(typeof(Program).Assembly);
using var container = new CompositionContainer(catalog);
var dummyInstance = container.GetExport<Foo>().Value;
Console.WriteLine($"HasInstance: {dummyInstance is not null}");
var export = container.GetExport<Foo>();
Console.WriteLine($"IsValueCreated: {export.IsValueCreated}");
}
}
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
public class Foo
{
}
The output is:
HasInstance: True
IsValueCreated: False
EDIT
I found a "solution" to that problem using a lot of reflection and magic field names:
var catalogExportProviderProperty = compositionContainer
.GetType()
.GetProperty("CatalogExportProvider", BindingFlags.Instance | BindingFlags.NonPublic) ??
throw ReflectionErrors.MissingProperty(
compositionContainer.GetType(),
"CatalogExportProvider");
var catalogExportProvider =
catalogExportProviderProperty.GetValue(compositionContainer) ??
throw new InvalidOperationException(
$@"Uninitialized property 'CatalogExportProvider' in {compositionContainer.GetType().Name}.");
var partsToDisposeField =
catalogExportProvider.GetType().GetField("_partsToDispose", BindingFlags.Instance | BindingFlags.NonPublic) ??
throw ReflectionErrors.MissingField(catalogExportProvider.GetType(), "_partsToDispose");
var partsToDispose = partsToDisposeField.GetValue(catalogExportProvider) as IEnumerable ??
throw new InvalidOperationException($@"Unable to retrieve disposable parts from {catalogExportProvider.GetType()}.");
foreach (var item in partsToDispose.OfType<object>())
{
var cachedInstanceProperty = item.GetType().GetProperty("CachedInstance", BindingFlags.Instance | BindingFlags.NonPublic) ??
throw ReflectionErrors.MissingProperty(item.GetType(), "CachedInstance");
var cachedInstance = cachedInstanceProperty.GetValue(item, index: null);
if (cachedInstance is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
}
This works because there is an implementation specification that every type that implements IAsyncDisposable
should also implement IDisposable
.
BUT: This solution doesn't feel right. I'm still looking for an official solution to that problem.
Upvotes: 0
Views: 87
Reputation: 3446
I found a solution by implementing a CustomCompositionContainer
.
The following points were taken into account:
Export
GetExportedValueCore
method, which can be overwritten.CompositionContainer
GetExportsCore
method can be overwritten. This is used to retrieve all exports of this CompositionContainer
.
Export
instances are returned.GetExportsCore
, the existing Export
instances are packed into the wrapper, which is then able to provide information about the instances.This derivation of CompositionContainer
provides access to all retrieved, shared instances:
public class CustomCompositionContainer : CompositionContainer
{
private readonly ConcurrentDictionary<object, object?> _retrievedSharedExports = new(ObjectReferenceEqualityComparer<object>.Default);
public CustomCompositionContainer(ComposablePartCatalog? catalog, CompositionOptions compositionOptions, params ExportProvider[]? providers)
: base(catalog, compositionOptions, providers)
{
}
public IEnumerable<object> RetrievedSharedExports => this._retrievedSharedExports.Keys;
protected override IEnumerable<Export>? GetExportsCore(ImportDefinition definition, AtomicComposition? atomicComposition)
{
var exports = base.GetExportsCore(definition, atomicComposition);
if (exports is null)
{
yield break;
}
foreach (var item in exports)
{
if ((CreationPolicy?)item.Metadata.GetValueOrDefault("System.ComponentModel.Composition.CreationPolicy") == CreationPolicy.Shared)
{
yield return new VerboseExport(item, OnExportedValueRetrieved);
continue;
}
yield return item;
}
void OnExportedValueRetrieved(object exportedValue) => this._retrievedSharedExports.TryAdd(exportedValue, null);
}
private sealed class VerboseExport : Export
{
private readonly Export _target;
private readonly Action<object> _onExportedValueRetrieved;
public VerboseExport(Export target, Action<object> onExportedValueRetrieved)
{
target.ThrowIfNull();
onExportedValueRetrieved.ThrowIfNull();
this._target = target;
this._onExportedValueRetrieved = onExportedValueRetrieved;
}
public override ExportDefinition Definition => this._target.Definition;
protected override object? GetExportedValueCore()
{
var result = this._target.Value;
if (result is not null)
{
this._onExportedValueRetrieved(result);
}
return result;
}
}
}
The solution created above can now be simplified to the following code:
foreach (var item in compositionContainer.RetrievedSharedExports.OfType<IAsyncDisposable>())
{
await item.DisposeAsync().ConfigureAwait(false);
}
This might not be the best solution but it's much cleaner than the previously used one that uses reflection for MEF internal types.
Upvotes: 0