Ontwikkelaars kunnen de levenscyclus van service-instanties kennen bij het gebruik van afhankelijkheid injectie, maar velen begrijpen niet volledig hoe dit werkt. U kunt online vele artikelen vinden die deze concepten duidelijker maken, maar ze zijn vaak hetzelfde als definities die u misschien al kent. Laat me dit illustreren met een gedetailleerd voorbeeld dat de uitleg simplificeert.
Bij het implementeren van afhankelijkheid injectie hebben ontwikkelaars drie opties die de levenscyclus van de instanties bepalen:
- Singleton
- Geadresseerd
- Transient
Vrijwel alle ontwikkelaars herkennen deze termen, maar een significante groep worstelt met het bepalen van welke optie ze moeten kiezen voor de levensduur van een service.
Definitie
Laat me beginnen met definities:
- Singleton service-instanties worden eenmaal per toepassing vanuit de servicecontainer gemaakt. Eén enkele instantie zal alle volgende aanvragen dienen. Singleton-services worden vernietigd aan het einde van de toepassing (d.w.z., bij het opnieuw starten van de toepassing).
- Transient service-instanties worden per aanvraag gemaakt vanuit de servicecontainer. Transient-services worden vernietigd aan het einde van de aanvraag.
- Scoped service-instanties worden eenmaal per clientaanvraag gemaakt. Transient-services worden vernietigd aan het einde van de aanvraag.
Toepassing
- Singleton – Wanneer u gebruik wilt maken van een enkele instantie van services gedurende de levensduur van de applicatie
- Transient – Wanneer u gebruik wilt maken van afzonderlijke instanties van services binnen het clientverzoek
- Scoped – Wanneer u een enkele instantie van de service wilt gebruiken voor elk verzoek
Wat is een clientverzoek? In erg simpele woorden, kunt u het beschouwen als een API/REST-oproep die naar uw applicatie komt door de knopklikken van de gebruiker om een reactie te krijgen.
Maak je geen zorgen, laten we het begrijpen aan de hand van een voorbeeld.
Voorbeeld
Maak eerst interfaces/services en klassen aan:
// we declareren 3 services zoals hieronder
Public interface ISingleton
Public interface ITransient
Public interface IScoped
Laat nu de implementatie voor elke service Interface/service die hierboven zijn gemaakt schrijven. We zullen proberen het concept te begrijpen door te proberen de variabelen callMeSingleton
, callMeTransient
, en callMeScoped
bij te werken.
- Implementatie van Singleton klasse:
class SingletonImplementation: ISingleton
{
var callMeSingleton = ""
// andere implementatie
public SetSingleton(string value)
{
callMeSingleton = value;
}
// andere implementatie
}
- Implementatie van Transient klasse:
class TransientImplementation: ITransient
{
var callMeTransient = ""
// andere implementatie
public SetTransient(string value)
{
callMeTransient = value;
}
// andere implementatie
}
- Implementatie van Scoped klasse:
class ScopedImplementation: IScoped
{
var callMeScoped = ""
// andere implementatie
public SetScoped(string value)
{
callMeScoped = value;
}
// andere implementatie
}
Registreer nu (ConfigureServices
) bij DI (Dependency Injection) om het levenscyclusbesluit voor elke service-instantie te maken:
services.AddSingleton<ISingleton, SingletonImplementation>();
services.AddTransient<ITransient , TransientImplementation>();
services.AddScoped<IScoped , ScopedImplementation>();
Laat ons deze diensten vanuit 3 verschillende klassen (ClassA
, ClassB
en ClassC
) aanroepen om het levenscyclus van elke service te begrijpen:
ClassA
:
public class ClassA
{
private ISingleton _singleton;
// Constructor om 3 verschillende services te instantiëren die we maken
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 implementaties
}
ClassB
:
public class ClassB
{
private ISingleton _singleton;
// Constructor om 3 verschillende services te instantiëren die we maken
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 implementaties
}
ClassC
:
public class ClassC
{
private ISingleton _singleton;
// Constructor om 3 verschillende services te instantiëren die we maken
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 implementaties
}
Analyse
Laat ons de resultaten en gedrag voor elke levenscyclus een voor een bekijken van bovenstaande implementatie:
Singleton
Alle klassen (ClassA
, ClassB
en ClassC
) zullen dezelfde enkele instantie van de klasse SingletonImplementation
gebruiken doorheen de hele levenscyclus van de applicatie. Dit betekent dat eigenschappen, velden en bewerkingen van de klasse SingletonImplementation
tussen de door alle aanroepende klassen gebruikte instanties worden gedeeld. Elke update aan eigenschappen of velden zal eerdere wijzigingen overschrijven.
Bijvoorbeeld, in het bovenstaande code, gebruiken ClassA
, ClassB
en ClassC
allemaal de service SingletonImplementation
als enkele instantie en bellen ze SetSingleton
om de variabele callMeSingleton
bij te werken. In dit geval zal er een enkele waarde zijn voor de variabele callMeSingleton
voor alle aanvragen die deze eigenschap willen benaderen. Het classetje dat laatst deze eigenschap aanroept om bij te werken, zal de waarde van callMeSingleton
overschrijven.
ClassA
– Het zal dezelfde instantie als de andere classes hebben voor de serviceTransientImplementation
.ClassB
– Het zal dezelfde instantie als de andere classes hebben voor de serviceTransientImplementation
.ClassC
– Het zal dezelfde instantie als de andere classes hebben voor de serviceTransientImplementation
.
ClassA
, ClassB
en ClassC
zijn dezelfde instantie van de klasse SingletonImplementation
bij te werken, wat de waarde van callMeSingleton
zal overschrijven. Daarom moet u voorzichtig zijn met het instellen of bijwerken van eigenschappen in de implementatie van de enkeltjes dienst.
Enkeltjes diensten worden afgehandeld aan het einde van de toepassing (d.w.z., bij het herstarten van de toepassing).
Transient
Alle klassen (`ClassA
`, `ClassB
` en `ClassC
`) zullen hun eigen instanties van de `TransientImplementation
` klasse gebruiken. Dit betekent dat als een klasse eigenschappen, velden of operaties van de `TransientImplementation
` klasse aanroept, zal hij alleen de waarden van zijn eigen instanties bijwerken of overschrijven. Wijzigingen in eigenschappen of velden worden niet gedeeld tussen andere instanties van `TransientImplementation
`.
Laten we het begrijpen:
ClassA
– Het zal zijn eigen instantie van dienst van `TransientImplementation
` hebben.ClassB
– Het zal zijn eigen instantie van dienst van `TransientImplementation
` hebben.ClassC
– Het zal zijn eigen instantie van dienst van `TransientImplementation
` hebben.
Stel dat u een `ClassD
` heeft die transiënte dienst aanroep van instanties van `ClassA
`, `ClassB
` en `ClassC
`. In dit geval zullen elke klasse instantie als verschillende/afzonderlijke instantie behandeld worden en elke klasse zal zijn eigen waarde van `callMeTransient
` hebben. Lees de inline commentaren voor ClassD
hieronder:
public ClassD
{
// andere implementatie
// De code onder deze lijn zal de waarde van callMeTransient naar "Ik komen uit ClassA" update voor het enige geval van ClassA alleen.
// En zal niet worden gewijzigd door latere aanroepen van Class B of B klasse
ClassA.UpdateTransientFromClassA();
// De code onder deze lijn zal de waarde van callMeTransient naar "Ik komen uit ClassB" update voor het enige geval van ClassB alleen.
// En zal geen waarde overschrijven voor het geval van ClassA noch worden gewijzigd door een volgende aanroep van Class C
ClassB.UpdateTransientFromClassB();
// De code onder deze lijn zal de waarde van callMeTransient naar "Ik komen uit ClassC" update voor het enige geval van ClassC alleen.
// En zal geen waarde overschrijven voor het geval van ClassA en ClassB noch worden gewijzigd door elke volgende aanroep van elke andere klasse.
ClassC.UpdateTransientFromClassC();
// andere implementatie
}
Transient services worden op het einde van elke aanvraag afgehandeld. Gebruik Transient als je een stateloze gedrag binnen de aanvraag wilt.
Gekend
Alle klassen (ClassA
, ClassB
en ClassC
) zullen voor elke aanvraag een enkele instantie van de klasse ScopedImplementation
gebruiken. Dit betekend dat aanroepen voor eigenschappen/velden/bewerkingen op de klasse ScopedImplementation
zullen plaatsvinden op een enkele instantie binnen het bereik van de aanvraag. Elke update van eigenschappen/velden zal worden gedeeld met andere klassen.
Laten we het begrijpen:
ClassA
– Het zal een instantie van de dienst vanTransientImplementation
hebben.ClassB
– Het zal zijnzelfde instantie van dienst vanTransientImplementation
hebben alsClassA
.ClassC
– Het zal zijnzelfde instantie van dienst vanTransientImplementation
hebben alsClassA
enClassB
.
Stel dat u een ClassD
heeft die een gescoped dienst aanroept vanaf instanties van ClassA
, ClassB
en ClassC
. In dit geval zal elke klasse een enkele instantie van de klasse ScopedImplementation
hebben. Lees de inline commentaar voor ClassD
hieronder.
public class ClassD
{
// andere implementatie
// Het volgende code zal de waarde van callMeScoped voor de instantie van ClassA bijwerken naar "Ik kom van ClassA"
// Maar omdat het een gescoped levenscyclus is, houdt het slechts een enkele instantie van ScopedImplementation bij
// Daarna kan het overschreden worden door de volgende aanroep uit ClassB of ClassC
ClassA.UpdateScopedFromClassA();
// Het volgende code zal de waarde van callMeScoped voor de enkele instantie ScopedImplementation bijwerken naar "Ik kom van ClassB"
// En het zal de waarde van callMeScoped voor de instantie van ClassA ook overschrijven.
ClassB.UpdateScopedFromClassB();
// Nu, als Class A enige operatie uitvoert op ScopedImplementation,
// zal het de meest recente eigenschappen/velden gebruiken die zijn overschreden door ClassB.
// Het volgende code zal de waarde van callMeScoped bijwerken naar "Ik kom van ClassC"
// En zal de waarde van callMeScoped voor de instanties van ClassA en ClassB ook overschrijven.
ClassC.UpdateScopedFromClassC();
// Als nu Class B of Class A enige operatie uitvoert op ScopedImplementation, zal het de meest recente eigenschappen/velden gebruiken die zijn overschreden door ClassC
// andere implementatie
}
Gescoped diensten worden bij het einde van elke aanvraag verwijderd. Gebruik Scoped als u een stateless gedrag wilt tussen individuele aanvragen.
Trivia Time
De levenscyclus van een dienst kan worden overschreden door een bovenliggende dienst waar het geïnitialiseerd wordt. Verward? Laat me u verklaren:
Laat’s hetzelfde voorbeeld van bovenstaande klassen gebruiken en initialiseer de Transient en Scoped services vanuit SingletonImplementation
(die een singleton is) zoals hieronder. Dat zou de ITransient
en IScoped
services initialiseren en de levenscyclus ervan overschrijven naar de singleton-levenscyclus als bovenliggende service. In dit geval zou uw toepassing geen Transient of Scoped services hebben (onder voorwaarde dat u alleen deze 3 services gebruikt in onze voorbeelden).
Lees door de regels in het onderstaande code:
public class SingletonImplementation: ISingleton
{
// constructor om de services te initialiseren.
private readonly ITransient _transient
private readonly IScoped _scoped
SingletonImplementation(ITransient transient, IScoped scoped)
{
_transient = transient;
// Nu zal _transient als singleton-service gedrag vertonen ongeacht hoe het was geregistreerd als Transient
_scoped = scoped;
// nu zal scoped gedrag als singleton-service vertonen ongeacht dat het is geregistreerd als Scoped
}
var callMeSingleton = ""
// andere implementatie
}
Samenvatting
Ik hoop dat de bovenstaande artikelen helpen om het onderwerp te begrijpen. Ik zou aanraden om het zelf uit te proberen met het bovenstaande context en u zult niet meer verward raken. Singleton is het gemakkelijkste te verstanden omdat de instantie ervan, zodra u hem creëert, gedeeld zal worden tussen toepassingen over de levenscyclus van de toepassing. Op dezelfde lijn als Singleton, imiteren Scoped-instanties hetzelfde gedrag maar alleen over de levenscyclus van een aanvraag binnen de toepassing. Transient is geheel stateloos, voor elke aanvraag en elke klasse-instantie zal deze haar eigen instantie van de service bevatten.
Source:
https://dzone.com/articles/understanding-the-dependency-injection-lifecycle