Begrijpen van de Dependency Injection Lifecycle: Singleton, Scoped en Transient Met Gedetailleerde Voorbeelden

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:

  1. Singleton
  2. Geadresseerd
  3. 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:

C#

 

	// 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:
C#

 

class SingletonImplementation: ISingleton
{
	var callMeSingleton = ""

	// andere implementatie
	public SetSingleton(string value)
	{
		callMeSingleton = value;
	}
	// andere implementatie
}

  • Implementatie van Transient klasse:
C#

 

class TransientImplementation: ITransient 
{
	var callMeTransient = ""
	
	// andere implementatie
	public SetTransient(string value)
	{
		callMeTransient = value;
	}
	// andere implementatie
}

  • Implementatie van Scoped klasse:
C#

 

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:

C#

 

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:
C#

 

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:
C#

 

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:
C#

 

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 service TransientImplementation.
  • ClassB – Het zal dezelfde instantie als de andere classes hebben voor de service TransientImplementation.
  • ClassC – Het zal dezelfde instantie als de andere classes hebben voor de service TransientImplementation.

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:

C#

 

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 van TransientImplementation hebben.
  • ClassB – Het zal zijnzelfde instantie van dienst van TransientImplementation hebben als ClassA.
  • ClassC – Het zal zijnzelfde instantie van dienst van TransientImplementation hebben als ClassA en ClassB.

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.

C#

 

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:

C#

 

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