Einsatz von Lazy<T, TMetadata> ohne MEF

Im Zusammenhang mit dem Managed Extensibility Framework (MEF) sind mir zum ersten mal die Klassen Lazy<T> und Lazy<T, TMetadata> über den Weg gelaufen. Während Lazy<T> ausreichend beschrieben wird, so ist Lazy<T, TMetadata> immer nur in Zusammenhang mit MEF zu finden. Dabei kann Lazy<T, TMetadata> auch ohne MEF sinnvoll eingesetzt werden.

Wie Lazy<T> und Lazy<T, TMetadata> in Zusammenhang mit MEF eingesetzt werden, habe ich schon ausreichend in MEF Teil 1 – Grundlagen, Imports und Exports und MEF Teil 2 – Metadaten und Erstellungsrichtlinien beschrieben. Wer wissen will, wie die Klasse Lazy<T> ohne MEF sinnvoll benutzt werden kann, dem sei der Blog von Hendrik Lösch empfohlen. Hilfreich ist auch der Artikel Lazy Initialization aus dem MSDN. Doch wie kann die Klasse Lazy<T, TMetadata> ohne MEF verwendet werden?

Während Lazy<T> direkt in mscorlib.dll enthalten ist, wurde Lazy<T, TMetadata> in System.ComponentModel.Composition.dll ‘versteckt’. Dementsprechend muss die Referenz auf die DLL dem Projekt hinzugefügt werden.

Im Klassendiagramm ist zu erkennen, dass die Klasse Lazy<T, TMetadata> von Lazy<T> abgeleitet wurde. Neben der Eigenschaft Metadata gibt es noch zusätzliche Konstruktoren, mit denen die Metadaten initialisiert werden.

ClassDiagram

Beispiel

Als Beispiel soll ein Logger dienen, der den Text in verschiedenen Formaten ausgeben kann. Hierzu werden zwei Klassen angelegt, die beide die gleiche Schnittstelle implementieren. Beide Klassen geben über die Methode WriteMessage() einen Text in unterschiedlichen Formaten aus.

public interface ILoggerClass
{
  void WriteMessage(string message);
}

public class LongFormatLogger : ILoggerClass
{
  public LongFormatLogger()
  {
    Console.WriteLine("LongFormatLogger constructor");
  }
  public void WriteMessage(string message)
  {
    Console.WriteLine("Message (" +
                      DateTime.Now.ToLongDateString() +
                      "): " +
                      message);
  }
}

public class ShortFormatLogger : ILoggerClass
{
  public ShortFormatLogger()
  {
    Console.WriteLine("ShortFormatLogger constructor");
  }
  public void WriteMessage(string message)
  {
    Console.WriteLine("Message (" +
                      DateTime.Now.ToShortDateString() +
                      "): " +
                      message);
  }
}

So weit so gut. Jetzt benötigen wir noch die Klasse für die Metadaten.

public class LoggerMetadata
{
  public string Destination { get; private set; }
  public LoggerMetadata(string destination)
  {
    this.Destination = destination;
  }
}

Die Metadaten sollen dazu dienen, Filterkriterien zu definieren, die entscheiden, welche Instanzen zur Ausgabe herangezogen werden. Sicherlich könnte man diese Angaben auch direkt in den Logger legen. Dieses würde aber bedeuten, dass jeder Logger instanziiert werden muss, um auf diese Eigenschaften zugreifen zu können. Durch die Verwendung der Klasse Lazy<T, TMetadata> ist der Zugriff auf die Metadaten möglich, ohne dass Instanzen der Logger existieren müssen.

private List<Lazy<ILoggerClass, LoggerMetadata>> LazyLoggerClasses { get; set; }

Da mehrere Varianten des Loggers zur Verfügung gestellt werden, sind diese in einer Eigenschaft vom Typ List<T> abgelegt. Die Loggerklasse und die Metadaten werden von Lazy<T, TMetadata> gekapselt.

private void CreateLogger()
{
  LazyLoggerClasses = new List<Lazy<ILoggerClass, LoggerMetadata>>();

  LazyLoggerClasses.Add(new Lazy<ILoggerClass, LoggerMetadata>(() =>
                                { return new ShortFormatLogger(); },
                        new LoggerMetadata("Trace")));

  LazyLoggerClasses.Add(new Lazy<ILoggerClass, LoggerMetadata>(() =>
                                { return new ShortFormatLogger(); },
                        new LoggerMetadata("Debug")));

  LazyLoggerClasses.Add(new Lazy<ILoggerClass, LoggerMetadata>(() =>
                                { return new LongFormatLogger(); },
                        new LoggerMetadata("Debugger")));
}

Im Konstruktor von Lazy<T, TMetadata> wird ein Delegate vom Typ Func<ILoggerClass> erwartet. Dieser Delegate gibt die gewünschte Instanz des Loggers zurück. Der zweite Parameter enthält das Objekt mit den Metadaten. Per Konstruktor wird der Klasse für die Metadaten ein String übergeben, der über die Eigenschaft Destination abgefragt werden kann.

Die Methode Write() nutzt diese Eigenschaft, um nur auf die gewünschten Logger zuzugreifen. Hierzu wird mit Hilfe eines Lambda-Ausdrucks und der Methode FindAll() eine Liste zurückgegeben, in der alle Elemente enthalten sind, die den Filterkriterien entsprechen. Die foreach-Schleife greift über die Eigenschaft Value der Klasse Lazy<T, TMetadaten> auf den Logger zu.

private void Write(string destination, string message)
{
  var outputs = LazyLoggerClasses.FindAll(a => a.Metadata.Destination.Contains(destination));
  foreach (var lazyDataClass in outputs)
    lazyDataClass.Value.WriteMessage(message);
}

Der Hauptteil des Beispiels ruft zuerst die Methode für das Erzeugen der Logger-Objekte auf. Anschließend werden (als Test) die Metadaten angezeigt und zum Schluss die Meldungen ausgegeben.

void Run()
{
  Console.WriteLine("-- Create some logger objects with metadata --");
  this.CreateLogger();
Console.WriteLine("-- Read the metadata --");
  foreach (var lazyDataClass in LazyLoggerClasses)
    Console.WriteLine(lazyDataClass.Metadata.Destination);
Console.WriteLine("-- Invoke the method of LoggerClass --");
  Write("Debug", "message for debug");
  Write("Trace", "message for trace");
  Write("Release", "message for release");
}

In der Ausgabe ist gut zu erkennen, dass erst dann der Konstruktor aufgerufen wird, wenn das erste Mal auf die Methode WriteMessage() zugegriffen wird. Der Zugriff auf die Metadaten erzeugt noch kein Objekt des Loggers.

CommandWindowSample01

Der Aufruf Write(“Debug”, “message for debug”) gibt zwei Meldungen aus, da als Filter der String Debug angegeben wurde und zwei Logger in den Metadaten (die Eigenschaft Destination) ebenfalls den String Debug bzw. Debugger enthalten.

Write(“Trace”, “message for trace”) gibt nur eine Meldung aus, da der String Trace nur auf einen Logger zutrifft.

Durch Write(”Release”, “message for release”) wird keine Meldung ausgegeben, da Release auf keinen der Logger zutrifft.

Beispiel 1 (Visual Studio 2010) auf GitHub

Auch wenn dieses ein eher ‘sinnfreies’ Beispiel ist, zeigt es doch, dass im Zusammenhang mit Lazy-Loading die Klasse Lazy<T, TMetadaten> sinnvoll eingesetzt werden kann.

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.

Leave a comment