Entwickler könnten sich der Lebenszyklus von Dienstinstanzen bewusst sein, wenn sie Dependency Injection verwenden, aber viele verstehen nicht vollständig, wie es funktioniert. Es gibt zahlreiche Artikel online, die diese Konzepte klären, aber oft wiederholen sie lediglich Definitionen, die du vielleicht bereits kennst. Ich möchte mit einem detaillierten Beispiel die Erklärung vereinfachen.
Beim Implementieren von Dependency Injection haben Entwickler drei Optionen, die den Lebenszyklus der Instanzen bestimmen:
- Singleton
- Scoped
- Transient
Obwohl die meisten Entwickler diese Begriffe kennen, ist es vielen schwer, die richtige Option für die Lebensdauer eines Diensts zu wählen.
Definitionen
Lass mich mit den Definitionen beginnen:
- Singleton -Dienstinstanzen mit Lebenszyklus werden einmal pro Anwendung vom Dienstcontainer erstellt. Eine einzelne Instanz dient allen folgenden Anfragen. Singleton-Dienste werden am Ende der Anwendung gelöscht (d.h., beim Neustart der Anwendung).
- Transient -Dienstinstanzen mit Lebenszyklus werden pro Anfrage vom Dienstcontainer erstellt. Transiente Dienste werden am Ende der Anfrage gelöscht.
- Scoped -Dienstinstanzen mit Lebenszyklus werden einmal pro Kundenanfrage erstellt. Transiente Dienste werden am Ende der Anfrage gelöscht.
Wann zu verwenden
- Singleton – Wenn Sie während der Lebensdauer der Anwendung nur eine Instanz eines Diensts verwenden möchten.
- Transient – Wenn Sie innerhalb der Clientanfrage einzelne Instanzen von Diensten verwenden möchten.
- Scoped – Wenn Sie für jede Anforderung eine einzelne Instanz eines Diensts verwenden möchten.
Was ist eine Clientanfrage? In sehr einfachen Worten, können Sie sie als API/REST-Aufruf betrachten, der durch die Klicks eines Benutzers auf Ihre Anwendung kommt, um eine Antwort zu erhalten.
Keine Sorge, lassen Sie uns das mit einem Beispiel verstehen.
Beispiel
Zuerst erstellen wir Interfaces/Dienste und Klassen:
// Wir deklarieren unten drei Dienste
Public interface ISingleton
Public interface ITransient
Public interface IScoped
Jetzt schreiben wir die Implementierung für jeden oben erstellten Dienst-Interface. Wir werden versuchen, das Konzept zu verstehen, indem wir versuchen, die Variablen callMeSingleton
, callMeTransient
und callMeScoped
zu aktualisieren.
- Implementierung der Singleton-Klasse:
class SingletonImplementation: ISingleton
{
var callMeSingleton = ""
// andere Implementierung
public SetSingleton(string value)
{
callMeSingleton = value;
}
// andere Implementierung
}
- Implementierung der Transient-Klasse:
class TransientImplementation: ITransient
{
var callMeTransient = ""
// andere Implementierung
public SetTransient(string value)
{
callMeTransient = value;
}
// andere Implementierung
}
- Implementierung der Scoped-Klasse:
class ScopedImplementation: IScoped
{
var callMeScoped = ""
// andere Implementierung
public SetScoped(string value)
{
callMeScoped = value;
}
// andere Implementierung
}
Lassen Sie uns die Dienste mit DI (Dependency Injection) registrieren (ConfigureServices
), um das Lebenszyklus von jeder Dienstinstanz zu bestimmen:
services.AddSingleton<ISingleton, SingletonImplementation>();
services.AddTransient<ITransient , TransientImplementation>();
services.AddScoped<IScoped , ScopedImplementation>();
Lass uns von drei verschiedenen Klassen (KlasseA
, KlasseB
und KlasseC
) diese Dienste verwenden/aufrufen, um das Lebenszyklus jedes Dienstes zu verstehen:
KlasseA
:
public class ClassA
{
private ISingleton _singleton;
// Konstruktor, um 3 unterschiedliche Dienste zu instantiieren, die wir erzeugen
public ClassA(ISingleton singleton,
ITransient _transient,
IScoped _scoped)
{
_singleton = singleton;
}
public void UpdateSingletonFromClassA()
{
_singleton.SetSingleton("I am from ClassA");
}
public void UpdateTransientFromClassA()
{
_transient.SetTransient("I am from ClassA");
}
public void UpdateScopedFromClassA()
{
_scoped.SetScoped("I am from ClassA");
}
// Andere Implementierung
}
KlasseB
:
public class ClassB
{
private ISingleton _singleton;
// Konstruktor, um 3 unterschiedliche Dienste zu instantiieren, die wir erzeugen
public ClassB(ISingleton singleton,
ITransient _transient,
IScoped _scoped)
{
_singleton = singleton;
}
public void UpdateSingletonFromClassB()
{
_singleton.SetSingleton("I am from ClassB");
}
public void UpdateTransientFromClassB()
{
_transient.SetTransient("I am from ClassB");
}
public void UpdateScopedFromClassB()
{
_scoped.SetScoped("I am from ClassB");
}
// Andere Implementierung
}
KlasseC
:
public class ClassC
{
private ISingleton _singleton;
// Konstruktor, um 3 unterschiedliche Dienste zu instantiieren, die wir erzeugen
public ClassC(ISingleton singleton,
ITransient _transient,
IScoped _scoped)
{
_singleton = singleton;
}
public void UpdateSingletonFromClassC()
{
_singleton.SetSingleton("I am from ClassC");
}
public void UpdateTransientFromClassC()
{
_transient.SetTransient("I am from ClassC");
}
public void UpdateScopedFromClassC()
{
_scoped.SetScoped("I am from ClassC");
}
// Andere Implementierung
}
Analyse
Lass uns die Ergebnisse und das Verhalten für jeden Lebenszyklus einer nach dem anderen von oben gegebenen Implementierung analysieren:
Singleton
Alle Klassen (KlasseA
, KlasseB
und KlasseC
) werden die gleiche einzige Instanz der Klasse SingletonImplementation
über den kompletten Lebenszyklus der Anwendung verwenden. Dies bedeutet, dass Eigenschaften, Felder und Operationen der Klasse SingletonImplementation
zwischen allen Instanzen, die in allen aufrufenden Klassen verwendet werden, geteilt werden. Jedes Update von Eigenschaften oder Feldern überschreibt vorherige Änderungen.
Die Klassen ClassA
, ClassB
und ClassC
verwenden alle den Service SingletonImplementation
als Singleton-Instanz und rufen SetSingleton
auf, um die Variable callMeSingleton
zu aktualisieren. In diesem Fall wird es für alle Anfragen, die auf diese Eigenschaft zugreifen versuchen, nur einen einzigen Wert der Variable callMeSingleton
geben. Die Klasse, die als letztes aufgerufen wird, um die Variable zu aktualisieren, wird den Wert von callMeSingleton
überschreiben.
ClassA
– Es wird dieselbe Instanz für den ServiceTransientImplementation
wie andere Klassen haben.ClassB
– Es wird dieselbe Instanz für den ServiceTransientImplementation
wie andere Klassen haben.ClassC
– Es wird dieselbe Instanz für den ServiceTransientImplementation
wie andere Klassen haben.
ClassA
, ClassB
und ClassC
aktualisieren dieselbe Instanz der Klasse SingletonImplementation
, die den Wert von callMeSingleton
überschreiben wird. Daher Vorsicht bei der Festlegung oder Aktualisierung von Eigenschaften in der Singleton-Service-Implementierung.
Singleton-Dienste werden am Ende der Anwendung gelöscht (d.h., bei einem Neustart der Anwendung).
Alle Klassen (ClassA
, ClassB
und ClassC
) werden ihre eigenen Instanzen der Klasse TransientImplementation
verwenden. Dies bedeutet, dass wenn eine Klasse Eigenschaften, Felder oder Operationen der Klasse TransientImplementation
aufruft, werden nur die individuellen Instanzwerte aktualisiert oder überschrieben. Jedes Update von Eigenschaften oder Feldern wird nicht zwischen anderen Instanzen der TransientImplementation
geteilt.
Lassen Sie uns verstehen:
ClassA
– Es wird ihre eigene Instanz des Dienstes vonTransientImplementation
haben.ClassB
– Es wird ihre eigene Instanz des Dienstes vonTransientImplementation
haben.ClassC
– Es wird ihre eigene Instanz des Dienstes vonTransientImplementation
haben.
Nehmen wir an, Sie haben eine ClassD
, die transienten Dienst von den Instanzen von ClassA
, ClassB
und ClassC
aufruft. In diesem Fall wird jede Klasseinstanz als unterschiedliche/separate Instanz behandelt und jeder Klasse hat ihren eigenen Wert für callMeTransient
. Lesen Sie die inline Kommentare unten für ClassD
:
public ClassD
{
// andere Implementierung
// Der folgende Code zeile wird den Wert von callMeTransient auf "Ich komme aus ClassA" aktualisieren, nur für das einzigartige Beispiel von ClassA.
// Und er wird nicht durch irgendwelche nächsten Aufrufe von Class B oder B Klasse geändert
ClassA.UpdateTransientFromClassA();
// Die folgende Code zeile wird den Wert von callMeTransient auf "Ich komme aus ClassB" aktualisieren, nur für das einzigartige Beispiel von ClassB.
// Und er wird weder den Wert für das ClassA Beispiel überschreiben, noch wird er durch einen nächsten Aufruf von Class C geändert
ClassB.UpdateTransientFromClassB();
// Die folgende Code zeile wird den Wert von callMeTransient auf "Ich komme aus ClassC" aktualisieren, nur für das einzigartige Beispiel von ClassC.
// Und er wird weder den Wert für die ClassA und ClassB Beispiele überschreiben, noch wird er durch einen irgendwelchen nächsten Aufruf von irgendwelcher anderen Klasse geändert
ClassC.UpdateTransientFromClassC();
// andere Implementierung
}
Transient Dienstleistungen werden am Ende jedes Anforderungen abgebrochen. Verwenden Sie Transient, wenn Sie ein stateless Verhalten innerhalb der Anforderung wünschen.
Scoped
Alle Klassen (ClassA
, ClassB
, und ClassC
) werden für jede Anforderung Single-Instanzen des ScopedImplementation
Klassen verwenden. Dies bedeutet, dass Aufrufe für Eigenschaften/Felder/Operationen auf der ScopedImplementation
Klasse auf einer einzigen Instanz innerhalb des Anforderungsbereichs erfolgen. Jeder Update von Eigenschaften/Feldern wird zwischen anderen Klassen geteilt.
Lassen Sie uns verstehen:
ClassA
– Es wird eine Instanz des Dienstes vonTransientImplementation
haben.ClassB
– Es wird dieselbe Instanz des Dienstes vonTransientImplementation
wieClassA
haben.ClassC
– Es wird dieselbe Instanz des Dienstes vonTransientImplementation
wieClassA
undClassB
haben.
Angenommen, Sie haben eine ClassD
, die den Scoped-Dienst von Instanzen von ClassA
, ClassB
und ClassC
aufruft. In diesem Fall wird jede Klasse eine einzelne Instanz der ScopedImplementation
-Klasse haben. Lesen Sie die Inline-Kommentare für ClassD
unten.
public class ClassD
{
// andere Implementierung
// Der folgende Code aktualisiert den Wert von callMeScoped auf "I am from ClassA" für die Instanz von ClassA
// Da es sich um einen Scoped-Lebenszyklus handelt, hält er eine einzelne Instanz von ScopedImplementation
// Dieser kann dann durch den nächsten Aufruf von ClassB oder ClassC überschrieben werden
ClassA.UpdateScopedFromClassA();
// Der folgende Code aktualisiert den Wert von callMeScoped auf "I am from ClassB" für die einzelne Instanz von ScopedImplementation
// Und er überschreibt auch den Wert von callMeScoped für die Instanz von classA.
ClassB.UpdateScopedFromClassB();
// Wenn nun Class A einige Operationen auf ScopedImplementation ausführt,
// verwendet es die neuesten Eigenschaften/ Feldeinträge, die von classB überschrieben wurden.
// Der folgende Code aktualisiert den Wert von callMeScoped auf "I am from ClassC"
// Und er überschreibt auch die Werte von callMeScoped für die Instanzen von classA und ClassB.
ClassC.UpdateScopedFromClassC();
// Wenn nun Class B oder Class A Operationen auf ScopedImplementation ausführen, verwenden sie die neuesten Eigenschaften/ Feldeinträge, die von classC überschrieben wurden
// andere Implementierung
}
Scoped-Dienste werden am Ende jeder Anfrage freigegeben. Verwenden Sie Scoped, wenn Sie ein stateless Verhalten zwischen einzelnen Anfragen wünschen.
Trivia-Zeit
Das Lebenszyklus eines Diensts kann von einem übergeordneten Dienst überschrieben werden, wo er initialisiert wird. Verwirrt? Ich erkläre es Ihnen:
Nehmen wir denselben Beispiel aus den oben genannten Klassen und initialisieren wir die flüchtigen und scoped Dienste aus SingletonImplementation
(der ein Singleton ist) wie unten gezeigt. Dies würde die ITransient
und IScoped
Dienste initialisieren und die Lebenszyklen dieser auf den Singleton-Lebenszyklus des übergeordneten Dienstes ändern. In diesem Fall hätte Ihre Anwendung keine flüchtigen oder scoped Dienste mehr (considern Sie, dass Sie nur diese 3 Dienste in unseren Beispielen verwendet haben).
Lesen Sie die Zeilen in dem untenstehenden Code durch:
public class SingletonImplementation: ISingleton
{
// Konstruktor, um die Dienste zu initialisieren.
private readonly ITransient _transient
private readonly IScoped _scoped
SingletonImplementation(ITransient transient, IScoped scoped)
{
_transient = transient;
// Jetzt verhalten sich _transient wie ein Singleton-Dienst, egal wie er als flüchtig registriert wurde
_scoped = scoped;
// Jetzt verhalten sich scoped wie Singleton-Dienste, egal ob sie als scoped registriert wurden
}
var callMeSingleton = ""
// andere Implementierung
}
Zusammenfassung
Ich hoffe, der obige Artikel ist hilfreich, um das Thema zu verstehen. Ich würde es empfehlen, es mit dem oben gegebenen Kontext selbst auszuprobieren und du wirst nie wieder verwirrt sein. Singleton ist die einfachste zu verstehen, weil es einmal eine Instanz erstellt, wird über den gesamten Lebenszyklus der Anwendung in der Anwendung geteilt. Auf der gleichen Art und Weise wie Singleton werden scoped Instanzen das gleiche Verhalten nachahmen, aber nur während des Lebenszyklus einer Anfrage in der Anwendung. Flüchtige sind völlig stateless, für jede Anfrage und jede Klasseinstanz wird ihre eigene Instanz des Dienstes behalten.
Source:
https://dzone.com/articles/understanding-the-dependency-injection-lifecycle