MEF Teil 6 – Constructor-Injection

Bei Constructor-Injection werden sämtliche Abhängigkeiten einer Klasse über den Konstruktor übergeben. Damit kann ein Objekt der Klasse nur erzeugt werden, wenn alle Abhängigkeiten bei der Erstellung vorhanden sind. Der Einsatz von Constructor-Injection mit dem Managed Extensibility Framework soll durch ein einfaches Beispiel vorgestellt werden.

Die genannten Abhängigkeiten entstehen z. B. durch den Verweis eines Objekt auf
ein anderes Objekt. Ohne die Anwendung von Constructor-Injection ist jedes Objekt selbst für die Erzeugung und Verwaltung seiner Abhängigkeiten zuständig.

Das Attribut ImportingConstructor

MEF benutzt zum Instanziieren von Komponenten immer den Standardkonstruktor. Soll ein anderer Konstruktor verwendet werden, so muss dieser mit dem Attribut ImportingConstructor dekoriert werden. Es darf nur ein Konstruktor das Attribut ImportingConstructor besitzen. Besitzen mehrere Konstruktoren das Attribut, kommt es zu einem Laufzeitfehler. Ebenfalls tritt ein Laufzeitfehler auf, wenn es keinen Standardkonstruktor gibt und kein Konstruktor mit dem Attribut ImportingConstructor dekoriert wurde. Interessant ist hier, dass die Konstruktoren ohne Probleme als private deklariert werden können. Alle Parameter des Konstruktors werden automatisch als Importe deklariert. Somit muss es in dem Host nur noch einen passenden Export geben.

Man kann (und man sollte auch) hier mit Vertragsnamen arbeiten. Spätestens wenn mehrere Parameter vom gleichen Typ vorhanden sind, ist dieses notwendig. Im Konstruktor wird hierzu das Attribut Import explizit vor jedem Parameter angegeben. Das Attribut Export im Host muss natürlich den gleichen Vertragsnamen erhalten.

Dekoration des Konstruktors mit dem Attribut ImportingConstructor bei einem Export.

[ImportingConstructor]
private Foo([Import("ConstructorParameter")]int parameter)
{
    Console.WriteLine(String.Format("Parameter: {0}.", parameter));
}

Definition einer Eigenschaft innerhalb eines Imports. Die Eigenschaft wird beim Instanziieren des Exports an den Parameter des Konstruktors übergeben.

[Export("ConstructorParameter")]
private int Parameter { get; set; }

Beispiel

Das folgende Beispiel ist ein einfacher Logger, der Meldungen auf die Konsole ausgibt. Der Logger ist in der Klasse ConsoleLogger implementiert. Die Ausgabe kann individuell formatiert werden. Hierzu dient die Klasse FormatterBase, von der es zwei Ableitungen gibt. Jede dieser Ableitungen (FormatterTimeStamp und FormatterDateTimeStamp) formatieren die Ausgabe unterschiedlich. Die Klasse ConsoleLogger erwartet in seinem Konstruktor ein Objekt der Klasse FormatterBase (oder eine Ableitung davon). Über dieses Objekt, das in den Konstruktor injiziert wird, wird der Text ausgegeben.

Die Klasse FormatterBase mit den beiden Ableitungen FormatterTimeStamp und FormatterDateTimeStamp.

using System;

namespace Formatter
{
    public class FormatterBase
    {
        public virtual string Format(string message)
        {
            return message;
        }
    }

    public class FormatterTimeStamp : FormatterBase
    {
        public override string Format(string message)
        {
            return string.Format("{0} - {1}",
                                 DateTime.Now.ToShortTimeString(),
                                 message);
        }
    }

    public class FormatterDateTimeStamp : FormatterBase
    {
        public override string Format(string message)
        {
            return string.Format("{0} {1} - {2}",
                                  DateTime.Now.ToShortDateString(),
                                  DateTime.Now.ToShortTimeString(),
                                  message);
        }
    }
}

Der Logger besitzt einen Konstruktor, der als Parameter ein Objekt vom Typ FormatterBase erwartet. Über dieses Objekt wird der Text formatiert und ausgegeben. Das Attribut ImportingConstructor gibt vor, welchen Konstruktor das Managed Extensibility Framework beim Instanziieren der Klasse benutzen soll. Der Parameter vom Konstruktor wurde hier explizit als Import deklariert.

using System;
using System.ComponentModel.Composition;
using Formatter;

namespace Logger
{
    [Export]
    public class ConsoleLogger
    {
        private FormatterBase formatter;

        [ImportingConstructor]
        public ConsoleLogger([Import(typeof(FormatterBase))]FormatterBase formatter)
        {
            this.formatter = formatter;
        }

        public void Log(string message)
        {
            string formattedString = this.formatter.Format(message);
            Console.WriteLine(formattedString);
        }
    }
}

Die Hauptanwendung hat eine Eigenschaft mit dem Namen Logger, in der der Logger enthalten ist. Diese Eigenschaft besitzt das Attribut Import. Eine weitere Eigenschaft, ist der Parameter für den Konstruktor. Diese Eigenschaft muss vom gleichen Typ sein und außerdem den gleichen Vertragsnamen besitzen wie der Parameter vom Konstruktor.

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using Formatter;
using Logger;

namespace Host
{
    class Program
    {
        [Import]
        public ConsoleLogger Logger { get; set; }

        [Export(typeof(FormatterBase))]
        private FormatterBase Formatter { get; set; }

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

            this.Formatter = new FormatterTimeStamp();
            //this.Formatter = new FormatterDateTimeStamp();

            container.ComposeParts(this);

            Logger.Log("Message");
        }
    }
}

Vor Aufruf der Methode ComposeParts() wird die Eigenschaft Formatter mit dem gewünschten Objekt des Formatters gesetzt. MEF übergibt diese Eigenschaft an den Konstruktor der Klasse ConsoleLogger. Auf diese Weise kann per Constructor-Injection festgelegt werden, wie die Ausgabe des Loggers formatiert wird.

CommandWindowsSample01

Das Beispiel wurde so erstellt, dass Hauptanwendung, Logger und Formatter in unterschiedlichen Assemblies liegen. So kann z.B. die DLL für den Formatter ausgetauscht werden, ohne dass die Hauptanwendung angefasst werden muss.

Beispiel 1 (Visual Studio 2010) auf GitHub

Author: Stefan Henneken

I’m Stefan Henneken, a software developer based in Germany. This blog is just a collection of various articles I want to share, mostly related to Software Development.

2 thoughts on “MEF Teil 6 – Constructor-Injection”

Leave a comment