MEF: difference between GetExportedValue and Satis

2019-05-07 15:10发布

问题:

We're using MEF (.NET 4, can't use 4.5 at the moment) in a MVVM application. Everything was fine until we needed to create models on the fly, for instance editable rows of a table. I didn't want to run into memory leaks, I found this article http://pglazkov.blogspot.ch/2011/04/mvvm-with-mef-viewmodelfactory.html and I discovered an unexpected behavior that I would like to understand. This is an Item added to the Shell.Items observable collection:

[PartCreationPolicy(CreationPolicy.NonShared)]
[Export]
public class Item : INotifyPropertyChanged, IDisposable
{
    [Import]
    private Lazy<Shell> shell;

    /// <summary>
    /// Initializes a new instance of the <see cref="Item"/> class.
    /// </summary>
    public Item()
    {
        this.Time = DateTime.Now;
    }

    ~Item()
    {
        this.Dispose(false);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public Shell Shell
    {
        get
        {
            return this.shell.Value;
        }
    }

    public DateTime Time { get; private set; }

    public void Initialize()
    {
        this.Shell.ItemsCount++;
    }

    public void Dispose()
    {
        this.Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.Shell.ItemsCount--;
        }
    }

    [..]
}

And this is the factory:

[PartCreationPolicy(CreationPolicy.Shared)]
[Export]
public class ChildContainerItemFactory : ItemFactory
{
    public override Item Create()
    {
        var container = ServiceLocator.Current.GetInstance<CompositionContainer>();
        using (var childContainer = CreateTemporaryDisposableContainer(container))
        {
            var item = childContainer.GetExportedValue<Item>();
            item.Initialize();
            return item;
        }
    }

    [..]
}

If I use this code, the Item is disposed together with the child container. If I change it to:

public override Item Create()
    {
        var container = ServiceLocator.Current.GetInstance<CompositionContainer>();
        using (var childContainer = CreateTemporaryDisposableContainer(container))
        {
            var item = new Item();
            childContainer.SatisfyImportsOnce(item);
            item.Initialize();
            return item;
        }
    }

The item is not disposed anymore with the container. I would like to understand if it is dangerous to use the GetExportedValue method (I use that method in other parts of the application) and which is the best practice to avoid memory leaks with for view models with a short lifetime.

Any help appreciated

回答1:

As far as I know (from experimenting and looking at MEF's source code):

  1. When a container is disposed, all exported catalog parts that are disposable are also disposed. The exported catalog parts are the ones either decorated with the ExportAttribute or specified as exports using the RegistrationBuilder (MEF2). These parts are created by the container and their lifetime is dependent on the lifetime of the container itself.
  2. On the other hand, objects manually created and composed using either CompositionContainer.SatisfyImportsOnce or CompositionContainer.ComposeParts will not be disposed. Their lifetime is not dependent on the lifetime of the container.

Now the difference between GetExportedValue and SatisfyImports is not the same. GetExportedValue returns all exported parts that are registered with the container. This includes the parts created by the container (the exported catalog parts mentioned in 1.) plus the parts registered using CompositionContainer.ComposeParts. SatisfyImports will inject any imports that are available but will not register the object as an export even if its class is marked as an exported type (see 1.). Additionally, SatisfyImports will disable recomposition, but this is off topic.

MEF's documentation on CodePlex provides valuable info on Parts Lifetime.