Gängige Muster für DelegateCommon Patterns for Delegates

VorherigesPrevious

Delegaten bieten einen Mechanismus, der Software-Entwürfe ermöglicht, die minimale Kopplung zwischen Komponenten umfassen.Delegates provide a mechanism that enables software designs involving minimal coupling between components.

Ein hervorragendes Beispiel für diese Art von Design ist LINQ.One excellent example for this kind of design is LINQ. Das LINQ-Abfrageausdrucksmuster stützt sich auf Delegaten für alle Funktionen.The LINQ Query Expression Pattern relies on delegates for all of its features. Betrachten Sie folgendes einfaches Beispiel:Consider this simple example:

var smallNumbers = numbers.Where(n => n < 10);

Die Sequenz von Zahlen wird auf nur die, die kleiner als der Wert 10 sind, gefiltert.This filters the sequence of numbers to only those less than the value 10. Die Where-Methode verwendet einen Delegaten, der bestimmt, welche Elemente einer Sequenz den Filter passieren.The Where method uses a delegate that determines which elements of a sequence pass the filter. Wenn Sie eine LINQ-Abfrage erstellen, geben Sie die Implementierung des Delegaten für diesen bestimmten Zweck an.When you create a LINQ query, you supply the implementation of the delegate for this specific purpose.

Der Prototyp für die Where-Methode ist:The prototype for the Where method is:

public static IEnumerable<TSource> Where<in TSource> (IEnumerable<TSource> source, Func<TSource, bool> predicate);

Dieses Beispiel wird mit allen Methoden wiederholt, die Teil von LINQ sind.This example is repeated with all the methods that are part of LINQ. Alle basieren auf Delegaten für den Code, der die jeweilige Abfrage verwaltet.They all rely on delegates for the code that manages the specific query. Dieses API-Entwurfsmuster ist ein sehr leistungsfähiges Muster zum Erlernen und Verstehen.This API design pattern is a very powerful one to learn and understand.

In diesem einfachen Beispiel wird veranschaulicht, wie Delegaten nur wenig Kopplung zwischen Komponenten erfordern.This simple example illustrates how delegates require very little coupling between components. Sie müssen keine Klasse erstellen, die von einer bestimmten Basisklasse abgeleitet ist.You don't need to create a class that derives from a particular base class. Sie müssen keine bestimmte Schnittstelle implementieren.You don't need to implement a specific interface. Die einzige Voraussetzung ist die Bereitstellung der Implementierung einer Methode, die für den jeweiligen Task unerlässlich ist.The only requirement is to provide the implementation of one method that is fundamental to the task at hand.

Erstellen eigener Komponenten mit DelegatenBuilding Your Own Components with Delegates

Erstellen Sie für dieses Beispiel mithilfe eines Entwurfs, das auf Delegaten basiert, eine Komponente.Let's build on that example by creating a component using a design that relies on delegates.

Definieren wir eine Komponente, die in einem umfangreichen System für Protokollmeldungen verwendet werden kann.Let's define a component that could be used for log messages in a large system. Bibliothekskomponenten können in vielen verschiedenen Umgebungen auf mehreren verschiedenen Plattformen verwendet werden.The library components could be used in many different environments, on multiple different platforms. Es gibt zahlreiche allgemeine Funktionen in der Komponente, die die Protokolle verwalten.There are a lot of common features in the component that manages the logs. Es muss Meldungen von jeder Komponente im System annehmen.It will need to accept messages from any component in the system. Diese Meldungen werden über unterschiedliche Prioritäten verfügen, die die Kernkomponente verwalten kann.Those messages will have different priorities, which the core component can manage. Die Meldungen sollten einen Zeitstempel in ihrer endgültigen archivierten Form aufweisen.The messages should have timestamps in their final archived form. Für komplexere Szenarios können Sie Meldungen von der Quellkomponente filtern.For more advanced scenarios, you could filter messages by the source component.

Es gibt ein Aspekt der Funktion, der sich häufig ändern wird: An welchem Standort Meldungen geschrieben werden.There is one aspect of the feature that will change often: where messages are written. In einigen Umgebungen können sie in der Fehlerkonsole geschrieben werden.In some environments, they may be written to the error console. In anderen Fällen in einer Datei.In others, a file. Andere können beispielsweise Datenbankspeicher, Betriebssystem-Ereignisprotokolle oder andere Dokumentspeicher sein.Other possibilities include database storage, OS event logs, or other document storage.

Es gibt auch Ausgabekombinationen, die in verschiedenen Szenarios verwendet werden könnten.There are also combinations of output that might be used in different scenarios. Möglicherweise möchten Sie Meldungen an die Konsole und in eine Datei schreiben.You may want to write messages to the console and to a file.

Ein Entwurf, basierend auf den Delegaten bietet ein hohes Maß an Flexibilität, und erleichtert Ihnen die Unterstützung von Speichermechanismen, die in der Zukunft möglicherweise hinzugefügt werden.A design based on delegates will provide a great deal of flexibility, and make it easy to support storage mechanisms that may be added in the future.

Durch dieses Design kann die primäre Protokollkomponente eine nicht virtuelle, sogar eine versiegelte Klasse sein.Under this design, the primary log component can be a non-virtual, even sealed class. Sie können eine beliebige Anzahl von Delegaten angeben, um die Meldungen in unterschiedliche Speichermedien zu schreiben.You can plug in any set of delegates to write the messages to different storage media. Die integrierte Unterstützung für Multicastdelegaten erleichtert die Unterstützung von Szenarios, in denen Meldungen an mehreren Standorten (eine Datei und eine Konsole) geschrieben werden müssen.The built in support for multicast delegates makes it easy to support scenarios where messages must be written to multiple locations (a file, and a console).

Erste ImplementierungA First Implementation

Fangen wir klein an: Die anfängliche Implementierung akzeptiert neue Meldungen, und schreibt mithilfe von angefügten Delegaten.Let's start small: the initial implementation will accept new messages, and write them using any attached delegate. Sie können mit einem Delegaten beginnen, der Meldungen in die Konsole schreibt.You can start with one delegate that writes messages to the console.

public static class Logger
{
    public static Action<string> WriteMessage;

    public static void LogMessage(string msg)
    {
        WriteMessage(msg);
    }
}

Die statische Klasse oben ist die einfachste Sache, die funktionieren kann.The static class above is the simplest thing that can work. Wir müssen die einzelne Implementierung für die Methode schreiben, die Meldungen in die Konsole schreibt:We need to write the single implementation for the method that writes messages to the console:

public static void LogToConsole(string message)
{
    Console.Error.WriteLine(message);
}

Abschließend müssen Sie den Delegaten verknüpfen, indem Sie ihn an den WriteMessage-Delegaten anfügen, der in der Protokollierung deklariert wurde:Finally, you need to hook up the delegate by attaching it to the WriteMessage delegate declared in the logger:

Logger.WriteMessage += LogToConsole;

MethodenPractices

Unser Beispiel ist bisher recht einfach, aber es veranschaulicht immer noch einige der wichtigen Richtlinien für Entwürfe, die Delegaten umfassen.Our sample so far is fairly simple, but it still demonstrates some of the important guidelines for designs involving delegates.

Delegattypen zu verwenden, die in der Kern-Framework definiert sind, erleichtert Benutzern die Arbeit mit den Delegaten.Using the delegate types defined in the Core Framework makes it easier for users to work with the delegates. Sie müssen keine neue Typen definieren, und Entwickler, die die Bibliothek nutzen, müssen keine neuen, spezialisierten Delegattypen erlernen.You don't need to define new types, and developers using your library do not need to learn new, specialized delegate types.

Die verwendeten Schnittstellen sind so minimal und so flexibel wie möglich: Um eine neue Ausgabeprotokollierung zu erstellen, müssen Sie eine Methode erstellen.The interfaces used are as minimal and as flexible as possible: To create a new output logger, you must create one method. Diese Methode kann eine statische Methode oder eine Instanzmethode sein.That method may be a static method, or an instance method. Es kann über jeden Zugriff verfügen.It may have any access.

Formatieren der AusgabeFormatting Output

Lassen Sie uns die erste Version etwas stabiler machen und anschließend andere Protokollierungsmechanismen erstellen.Let's make this first version a bit more robust, and then start creating other logging mechanisms.

Als Nächstes fügen wir einige Argumente in die LogMessage()-Methode ein, damit Ihre Protokollklasse mehr strukturierte Meldungen erstellt:Next, let's add a few arguments to the LogMessage() method so that your log class creates more structured messages:

// Logger implementation two
public enum Severity
{
    Verbose,
    Trace,
    Information,
    Warning,
    Error,
    Critical
}

public static class Logger
{
    public static Action<string> WriteMessage;

    public static void LogMessage(Severity s, string component, string msg)
    {
        var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
        WriteMessage(outputMsg);
    }
}

Als Nächstes verwenden wir dieses Severity-Argument, um die Meldungen zu filtern, die in das Ausgabeprotokoll gesendet werden.Next, let's make use of that Severity argument to filter the messages that are sent to the log's output.

public static class Logger
{
    public static Action<string> WriteMessage;

    public static Severity LogLevel {get;set;} = Severity.Warning;

    public static void LogMessage(Severity s, string component, string msg)
    {
        if (s < LogLevel)
            return;

        var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
        WriteMessage(outputMsg);
    }
}

MethodenPractices

Sie haben der Protokollierungsinfrastruktur neue Funktionen hinzugefügt.You've added new features to the logging infrastructure. Da die Protokollierungskomponente sehr lose an Ausgabemechanismen gekoppelt ist, können diese neuen Funktionen ohne Auswirkung auf den Code, der die Protokollierungsdelegaten implementiert, hinzugefügt werden.Because the logger component is very loosely coupled to any output mechanism, these new features can be added with no impact on any of the code implementing the logger delegate.

Solange Sie dies erstellen, sehen Sie weitere Beispiele, wie diese lose Verbindung mehr Flexibilität bei der Aktualisierung von Teilen der Site ohne Änderungen an anderen Speicherorten ermöglicht.As you keep building this, you'll see more examples of how this loose coupling enables greater flexibility in updating parts of the site without any changes to other locations. In der Tat könnten die Klassen der Protokollierungsausgabe in einer umfangreicheren Anwendung in einer anderen Assembly sein. Sie müssen auch nicht neu erstellt werden.In fact, in a larger application, the logger output classes might be in a different assembly, and not even need to be rebuilt.

Erstellen eines zweiten AusgabemodulsBuilding a Second Output Engine

Die Protokollierungskomponente kommt gut voran.The Log component is coming along well. Fügen wir ein weiteres Ausgabemodul hinzu, das Meldungen in einer Datei protokolliert.Let's add one more output engine that logs messages to a file. Dies wird ein etwas komplexeres Ausgabemodul werden.This will be a slightly more involved output engine. Es wird eine Klasse sein, die die Dateivorgänge kapselt, und sicherstellt, dass die Datei nach jedem Schreibvorgang immer geschlossen wird.It will be a class that encapsulates the file operations, and ensures that the file is always closed after each write. Dadurch wird sichergestellt, dass alle Daten auf den Datenträger geschrieben werden, nachdem jede Meldung generiert wurde.That ensures that all the data is flushed to disk after each message is generated.

Hier ist diese dateibasierte-Protokollierung:Here is that file based logger:

public class FileLogger
{
    private readonly string logPath;
    public FileLogger(string path)
    {
        logPath = path;
        Logger.WriteMessage += LogMessage;
    }

    public void DetachLog() => Logger.WriteMessage -= LogMessage;

    // make sure this can't throw.
    private void LogMessage(string msg)
    {
        try {
            using (var log = File.AppendText(logPath))
            {
                log.WriteLine(msg);
                log.Flush();
            }
        } catch (Exception e)
        {
            // Hmm. Not sure what to do.
            // Logging is failing...
        }
    }
}

Wenn Sie diese Klasse erstellt haben, können Sie sie instanziieren und sie fügt ihre LogMessage-Methode in die Protokollierungskomponente an:Once you've created this class, you can instantiate it and it attaches its LogMessage method to the Logger component:

var file = new FileLogger("log.txt");

Diese beiden schließen einander nicht aus.These two are not mutually exclusive. Sie können beide Protokollmethoden anfügen, und Meldungen in die Konsole und eine Datei generieren:You could attach both log methods and generate messages to the console and a file:

var fileOutput = new FileLogger("log.txt");
Logger.WriteMessage += LogToConsole;

Später können Sie auch in derselben Anwendung einen Delegaten ohne andere Probleme auf dem System entfernen:Later, even in the same application, you can remove one of the delegates without any other issues to the system:

Logger.WriteMessage -= LogToConsole;

MethodenPractices

Sie haben nun einen zweiten Ausgabehandler für das Protokollierungs-Subsystem hinzugefügt.Now, you've added a second output handler for the logging sub-system. Dieses Objekt benötigt ein wenig mehr Infrastruktur, um das Dateisystem ordnungsgemäß zu unterstützen.This one needs a bit more infrastructure to correctly support the file system. Bei dem Delegat handelt es sich um eine Instanzmethode.The delegate is an instance method. Es ist auch eine private Methode.It's also a private method. Es besteht keine Notwendigkeit für höhere Verfügbarkeit, da die Delegat-Infrastruktur die Delegaten verbinden kann.There's no need for greater accessibility because the delegate infrastructure can connect the delegates.

Zweitens ermöglicht der delegatbasierte Entwurf mehrere Ausgabemethoden ohne zusätzlichen Code.Second, the delegate-based design enables multiple output methods without any extra code. Sie müssen keine zusätzliche Infrastruktur zur Unterstützung von mehreren Ausgabemethoden erstellen.You don't need to build any additional infrastructure to support multiple output methods. Sie erhalten einfach eine andere Methode auf der Aufrufliste.They simply become another method on the invocation list.

Achten Sie besonders auf den Code in der Ausgabemethode, die die Datei protokolliert.Pay special attention to the code in the file logging output method. Es wird codiert, um sicherzustellen, dass keine Ausnahmen ausgelöst werden.It is coded to ensure that it does not throw any exceptions. Obwohl dies nicht immer unbedingt erforderlich ist, ist es häufig sinnvoll.While this isn't always strictly necessary, it's often a good practice. Wenn eine der Delegatmethoden eine Ausnahme auslöst, werden die im Aufruf verbleibenden Delegaten nicht aufgerufen werden.If either of the delegate methods throws an exception, the remaining delegates that are on the invocation won't be invoked.

Ein letzter Hinweis: Die Dateiprotokollierung muss ihre Ressourcen durch Öffnen und Schließen der Datei in jeder Protokollmeldung verwalten.As a last note, the file logger must manage its resources by opening and closing the file on each log message. Sie können die Datei offen lassen und IDisposable implementieren, um die Datei zu schließen, wenn Sie fertig sind.You could choose to keep the file open and implement IDisposable to close the file when you are completed. Beide Methoden haben Vor- und Nachteile.Either method has its advantages and disadvantages. Beide erstellen etwas mehr Kopplung zwischen den Klassen.Both do create a bit more coupling between the classes.

Kein Teil des Codes in der Protokollierungsklasse müsste aktualisiert werden, um beide Szenarios zu unterstützen.None of the code in the Logger class would need to be updated in order to support either scenario.

Behandlung von NULL-DelegatenHandling Null Delegates

Zum Schluss aktualisieren wir die LogMessage-Methode, damit es für jene Fälle stabil ist, wenn kein Ausgabemechanismus ausgewählt wurde.Finally, let's update the LogMessage method so that it is robust for those cases when no output mechanism is selected. Die aktuelle Implementierung löst eine NullReferenceException aus, wenn der WriteMessage-Delegat nicht über eine angefügte Aufrufliste verfügt.The current implementation will throw a NullReferenceException when the WriteMessage delegate does not have an invocation list attached. Möglicherweise bevorzugen Sie einen Entwurf, der im Hintergrund fortgesetzt wird, wenn keine Methoden angefügt wurden.You may prefer a design that silently continues when no methods have been attached. Dies lässt sich mithilfe des bedingten NULL-Operators, kombiniert mit der Delegate.Invoke()-Methode, leicht umsetzen:This is easy using the null conditional operator, combined with the Delegate.Invoke() method:

public static void LogMessage(string msg)
{
    WriteMessage?.Invoke(msg);
}

Der bedingte NULL-Operator (?.) wird verkürzt, wenn der linke Operand (WriteMessage in diesem Fall) NULL ist, was bedeutet, dass nicht versucht wurde, eine Meldung zu protokollieren.The null conditional operator (?.) short-circuits when the left operand (WriteMessage in this case) is null, which means no attempt is made to log a message.

Sie werden die Invoke()-Methode nicht in der Dokumentation für System.Delegate oder System.MulticastDelegate aufgelistet finden.You won't find the Invoke() method listed in the documentation for System.Delegate or System.MulticastDelegate. Der Compiler generiert eine typsicherer Invoke-Methode für jeden deklarierten Delegattyp.The compiler generates a type safe Invoke method for any delegate type declared. In diesem Beispiel bedeutet das, dass Invoke ein einzelnes string-Argument nimmt und über einen void-Rückgabetyp verfügt.In this example, that means Invoke takes a single string argument, and has a void return type.

Zusammenfassung der MethodenSummary of Practices

Sie haben die Anfänge einer Protokollkomponente gesehen, die mit anderen Writern und anderen Funktionen erweitert werden konnte.You've seen the beginnings of a log component that could be expanded with other writers, and other features. Mithilfe von Delegaten im Entwurf sind diese verschiedenen Komponenten sehr lose gekoppelt.By using delegates in the design these different components are very loosely coupled. Dies bietet mehrere Vorteile.This provides several advantages. Es ist sehr einfach neue Ausgabemechanismen zu erstellen und diese an das System anzufügen.It's very easy to create new output mechanisms and attach them to the system. Diese Mechanismen benötigen nur eine Methode: Die Methode, die die Protokollmeldungen schreibt.These other mechanisms only need one method: the method that writes the log message. Es ist ein Entwurf, der sehr stabil ist, wenn neue Funktionen hinzugefügt werden.It's a design that is very resilient when new features are added. Der für alle Writer erforderliche Vertrag dient dazu, eine Methode zu implementieren.The contract required for any writer is to implement one method. Diese Methode kann eine statische oder Instanzmethode sein.That method could be a static or instance method. Es kann sich um einen öffentlichen, privaten oder jeden anderen rechtlichen Zugriff handeln.It could be public, private, or any other legal access.

Die Protokollierungsklasse kann eine beliebige Anzahl von Verbesserungen oder Änderungen vornehmen, ohne wichtige Änderungen einzuführen.The Logger class can make any number of enhancements or changes without introducing breaking changes. Wie jede Klasse können Sie die öffentliche API nicht ohne das Risiko von wichtigen Änderungen ändern.Like any class, you cannot modify the public API without the risk of breaking changes. Aber da die Kopplung zwischen der Protokollierung und den Ausgabemodulen nur über den Delegaten stattfindet, sind keine anderen Typen (z.B. Schnittstellen oder Basisklassen) beteiligt.But, because the coupling between the logger and any output engines is only through the delegate, no other types (like interfaces or base classes) are involved. Die Kopplung ist so klein wie möglich.The coupling is as small as possible.

WeiterNext