Home > Managed Extensibility Framework > MEF Teil 3 – Lifecycle beeinflussen und überwachen

MEF Teil 3 – Lifecycle beeinflussen und überwachen

Das Binden der Composable Parts wurde im 1. Teil ausführlich vorgestellt. Bei einer Anwendung kann es aber notwendig sein, solche Verbindungen gezielt wieder aufzulösen ohne gleich den ganzen Container zu löschen. Des weiteren werden Schnittstellen vorgestellt, die die Parts darüber informieren, ob deren Verbindung hergestellt, oder der Part komplett gelöscht wurde.

Das Interface IPartImportsSatisfiedNotification

Für die Parts kann es hilfreich sein zu erfahren, wann das Binden abgeschlossen ist. Hierzu wird die Schnittstelle IPartImportsSatisfiedNotification implementiert. Die Schnittstelle kann sowohl im Import, als auch im Export implementiert werden.

[Export(typeof(ICarContract))]
public class BMW : ICarContract, IPartImportsSatisfiedNotification
{
    // ...
    public void OnImportsSatisfied()
    {
        Console.WriteLine("BMW import is satisfied.");
    }
}
class Program : IPartImportsSatisfiedNotification
{
    [ImportMany(typeof(ICarContract))]
    private IEnumerable<Lazy<ICarContract>> CarParts { get; set; }

    static void Main(string[] args)
    {
        new Program().Run();
    }
    void Run()
    {
        var catalog = new DirectoryCatalog(".");
        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
        foreach (Lazy<ICarContract> car in CarParts)
            Console.WriteLine(car.Value.StartEngine("Sebastian"));
        container.Dispose();
    }
    public void OnImportsSatisfied()
    {
        Console.WriteLine("CarHost imports are satisfied.");
    }
}

Beispiel 1 (Visual Studio 2010)

Bei der Ausführung des Programms wird nach container.ComposeParts() (Zeile 14) die Methode OnImportsSatisfied() aufgerufen. Wird zum ersten Mal auf ein Export zugegriffen, wird von diesem erst der Konstruktor, dann die Methode OnImportsSatisfied() und zum Schluss die Methode StartEngine() ausgeführt.

Die Reihenfolge der Methodenaufrufe ändert sich deutlich, wenn nicht mit der Klasse Lazy<T> gearbeitet wird. In diesem Fall wird nach der Methode container.ComposeParts() erst der Konstruktor und dann die Methode OnImportsSatisfied() von allen Exports ausgeführt. Erst zum Schluss wird OnImportsSatisfied() aufgerufen.

Die Verwendung von IDisposable

Wie in .NET üblich, sollten auch die Exports die Schnittstelle IDisposable implementieren. Da das Managed Extensibility Framework die Parts verwaltet, sollte auch nur der Container, der die Parts beinhaltet, Dispose() aufrufen. Wird vom Container Dispose() aufgerufen, so ruft er von allen Parts ebenfalls Dispose() auf. Deshalb ist es wichtig, die Methode Dispose() vom Container aufzurufen, wenn dieser nicht mehr benötigt wird.

Freigabe von Exports

Wurde die Erstellungsrichtinie (Creation Policy) auf NonShared gesetzt, so werden gleiche Exports mehrfach erstellt. Diese werden erst dann wieder freigegeben, wenn der gesamte Container mit der Methode Dispose() zerstört wird. Gerade bei lang lebigen Anwendungen kann dieses zu Problemen führen. Deshalb besitzt die Klasse CompositionContainer die Methoden ReleaseExports() und ReleaseExport(). ReleaseExports() zerstört alle Parts, während ReleaseExport() nur einzelne Parts freigibt. Bei der Freigabe wird von jedem Export Dispose() aufgerufen, wenn von diesem die Schnittstelle IDisposable implementiert wurde. Somit lassen sich gezielt Exports wieder aus dem Container entfernen, ohne den ganzen Container zerstören zu müssen. Die Methoden ReleaseExports() und ReleaseExport() können nur auf Exports angewendet werden, deren Erstellungsrichtlinie auf NonShared steht.

Bei dem folgenden Beispiel wurde in jedem Export die Schnittstelle IDisposable implementiert.

using System;
using System.ComponentModel.Composition;
using CarContract;
namespace CarBMW
{
    [Export(typeof(ICarContract))]
    public class BMW : ICarContract, IDisposable
    {
        private BMW()
        {
            Console.WriteLine("BMW constructor.");
        }
        public string StartEngine(string name)
        {
            return String.Format("{0} starts the BMW.", name);
        }
        public void Dispose()
        {
            Console.WriteLine("Disposing BMW.");
        }
    }
}

Der Host bindet zuerst alle Exports auf den Import. Nach dem Aufruf der Methode StartEngine() werden alle Exports durch die Methode ReleaseExports() wieder freigegeben. Nach erneutem Binden der Exports auf den Import werden dieses Mal die Exports einzeln entfernt. Zum Schluss wird durch die Methode Dispose() der Container zerstört.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using CarContract;
namespace CarHost
{
    class Program
    {
        [ImportMany(typeof(ICarContract), RequiredCreationPolicy = CreationPolicy.NonShared)]
        private IEnumerable<Lazy<ICarContract>> CarParts { get; set; }

        static void Main(string[] args)
        {
            new Program().Run();
        }
        void Run()
        {
            var catalog = new DirectoryCatalog(".");
            var container = new CompositionContainer(catalog);

            container.ComposeParts(this);
            foreach (Lazy<ICarContract> car in CarParts)
                Console.WriteLine(car.Value.StartEngine("Sebastian"));

            Console.WriteLine("");
            Console.WriteLine("ReleaseExports.");
            container.ReleaseExports<ICarContract>(CarParts);
            Console.WriteLine("");

            container.ComposeParts(this);
            foreach (Lazy<ICarContract> car in CarParts)
                Console.WriteLine(car.Value.StartEngine("Sebastian"));

            Console.WriteLine("");
            Console.WriteLine("ReleaseExports.");
            foreach (Lazy<ICarContract> car in CarParts)
                container.ReleaseExport<ICarContract>(car);

            Console.WriteLine("");
            Console.WriteLine("Dispose Container.");
            container.Dispose();
        }
    }
}

Die Ausgabe des Programms sieht dementsprechend wie folgt aus:

CommandWindowSample02

Beispiel 2 (Visual Studio 2010)

Ausblick

Im 4. Teil geht es um Vererbung bei den Composable Parts. Wie verhält sich MEF, wenn Klassen das Attribut Import oder Export enthalten und diese Klassen innerhalb einer Vererbungshierarchie enthalten sind.

Advertisements
  1. March 11, 2012 at 12:10 am

    Prima Reihe zum Thema MEF.
    Frage zu diesem Teil: OnImportsSatisfied -> was wäre denn ein klassischer Anwendungsfall, wenn die Methode aufgerufen wird, was behandelt man im Rahmen dieses Events? Das ist mir nicht klar geworden.

    Implementierung von IDisposable: den Container erstelle ich mit einer using-Anweisung, soweit so gut, wozu dann noch IDisposable auf die Exportklasse? Der Catalog wird in den Beispielen immer innerhalb der Methode deklariert…

    • March 29, 2012 at 10:06 pm

      OnImportsSatisfied ist z.B. bei den Exports sinnvoll. Ein Export kann über den Konstruktor weitere Imports erhalten. Der Zugriff auf diese Elemente ist aber erst dann möglich, wenn das Komposen komplett abgeschlossen ist. Und genau dieses wird durch die Methode OnImportsSatisfied bekannt gegeben.

      Wird der Container durch die Methode Dispose zerstört (genau das passiert ja bei der using-Anweisung), so wird auch bei jedem Export Dispose aufgerufen. Dazu muss das zugehörige Export natürlich das Interface IDisposable implementiert haben. Somit hat jeder Export die Gelegenheit „aufzuräumen“.

  2. Felix Liebrecht
    October 30, 2012 at 9:51 am

    Hallo,

    ein ausgezeichnetes Tutorial.

    Ich habe in Sample02 im Host ab Zeile 39 noch einmal gebunden:

    Console.WriteLine(“”);

    container.ComposeParts(this);
    foreach (Lazy car in CarParts)
    Console.WriteLine(car.Value.StartEngine(“Sebastian”));

    um zu demonstrieren, dass ein Aufruf von Dispose auf dem Container ein Aufruf von Dispose auf jedem Export nachsichzieht, wie in Deiner Antwort vom 29.03.2013, Absatz 2, ausgeführt.

    Gruß
    Felix

  3. Thomas
    October 30, 2012 at 9:55 pm

    Hallo, durch ReleaseExports wird zwar auf jedem Export Dispose() aufgerufen, man kann die Exports im Host aber einfach weiterverwenden, ohne sie durch container.ComposeParts wieder aufbauen und binden zu müssen. Also sind die Export-Instanzen im Host doch noch völlig intakt? Was meinst du?

    Console.WriteLine(“ReleaseExports.”);
    container.ReleaseExports(CarParts);
    Console.WriteLine(“”);

    foreach (Lazy car in CarParts)
    Console.WriteLine(car.Value.StartEngine(“Sebastian”));

    danke fürs gute Tutorial,
    Thomas

    • Florian
      March 4, 2014 at 1:40 pm

      Zur Behauptung von Thomas (30.10.2012):
      Diese Fragestellung hat mit MEF meines Erachtens nichts am Hut.
      Korrigiert mich bitte, wenn ich falsch liege:
      Nur weil Du eine Methode namens “Dispose” aufrufst, ist das Objekt doch nicht gleich zerstört?
      Führe “CarParts.GetEnumerator().Dispose();” aus – Du kannst weiterhin durch CarParts iterieren.
      “Zerstören” kannst Du die Export-Instanzen, indem Du CarParts = null setzt und damit einfach keinen Zugriff mehr auf jene hast.
      Selbst dann fliegen sie noch irgendwo herum, bis der GC sie zermalmt.

      Dem ungehindert sollte man Dispose trotzdem verwenden und sinnvoll einbinden (siehe http://msdn.microsoft.com/de-de/library/66x5fx1b.aspx).

      Das Tutorial geht gut rein, vielen Dank dafür!

  1. December 7, 2011 at 8:26 am
  2. January 21, 2013 at 11:33 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: