0

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

DEMO

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.

Sebastian Schumann
  • 3,204
  • 19
  • 37
  • The the Lazy returned isnt shared, but the Value should be. – mxmissile Apr 28 '22 at 16:14
  • @mxmissile Yes that's the problem. I don't really understand why they don't share the lazy instance. Lazy is able to create instances threadsafe - so multithreading should not be the reason. For that non-shared reason I'm not able to detect whether the instance is already created or not. I don't want to create an unnecessary instance only to free them. – Sebastian Schumann Apr 28 '22 at 16:29
  • It's only creating a new instance of Lazy<>, which is extremely light. Trust the framework, Foo is shared. https://dotnetfiddle.net/mBi4Sg – mxmissile Apr 28 '22 at 16:41
  • @mxmissile I know that Foo is shared! That's the purpose of `[PartCreationPolicy(CreationPolicy.Shared)]`. And yeah it's light-wight - but still not a reason not to cache that instance - a benefit would be that one can decide whether the instance is already created. What I try to do: At the application shutdown I try to find all allready created instances of MEF to free them because MEF does not recognize `IAsyncDisposable`. I don't want to create instances that hasn't been used so far. We shut down the application. Hope I'm clear now. – Sebastian Schumann Apr 28 '22 at 16:57
  • Honestly this is the first I've ever looked at MEF, so I'm not much help (hence comments vs answer). So hopefully someone more knowledgeable chimes in to help with the disposable question. – mxmissile Apr 28 '22 at 17:03

0 Answers0