I Sviluppatori potrebbero essere a conoscenza del ciclo di vita delle istanze di servizio quando utilizzano la iniezione di dipendenze, ma molti non comprendono appieno come funziona. Si possono trovare numerosi articoli online che chiariscono questi concetti, ma spesso si tratta solo di ripetere definizioni che forse già conoscete. Permettetemi di illustrare con un esempio dettagliato che semplifica l’explicazione.
nell’implementazione dell’iniezione di dipendenze, gli sviluppatori hanno tre opzioni che determinano il ciclo di vita delle istanze:
- Singleton
- Scoped
- Transient
Mentre la maggior parte degli sviluppatori riconosce questi termini, un numero significativo ha difficoltà a determinare qual’opzione scegliere per il ciclo di vita di un servizio.
Definizioni
Permettetemi di iniziare con le definizioni:
- Singleton il servizio di istanza con ciclo di vita creato una sola volta per applicazione dal contenitore di servizi. Un’unica istanza sarà in grado di servire tutte le richieste successive. I servizi Singleton sono disposti alla fine dell’applicazione (cioè al riavvio dell’applicazione).
- Transient il servizio di istanza con ciclo di vita creato per ogni richiesta dal contenitore di servizi. I servizi Transient sono disposti alla fine della richiesta.
- Scoped il servizio di istanza con ciclo di vita creato una sola volta per richiesta cliente. I servizi Transient sono disposti alla fine della richiesta.
Quando usare
- Singleton – Quando si desidera utilizzare istanze singole dei servizi per tutta la durata dell’applicazione
- Transient – Quando si desidera utilizzare istanze individuali dei servizi all’interno della richiesta del client
- Scoped – Quando si desidera utilizzare un’unica istanza di servizio per ogni richiesta
Cosa è una richiesta client? In parole semplicissime, puoi considerarla una chiamata API/REST che arriva alla tua applicazione attraverso i clic del pulsante dell’utente per ottenere la risposta.
Non preoccuparti, diamo un’occhiata a un esempio.
Esempio
Prima, creiamo interfacce/servizi e classi:
// dichiariamo 3 servizi come di seguito
Public interface ISingleton
Public interface ITransient
Public interface IScoped
Ora scriviamo l’implementazione per ciascun servizio Interfaccia/servizio creato sopra. Cercheremo di capire il concetto provando a aggiornare la variabile callMeSingleton
, callMeTransient
, e callMeScoped
.
- Implementazione classe Singleton:
class SingletonImplementation: ISingleton
{
var callMeSingleton = ""
// altra implementazione
public SetSingleton(string value)
{
callMeSingleton = value;
}
// altra implementazione
}
- Implementazione classe Transient:
class TransientImplementation: ITransient
{
var callMeTransient = ""
// altra implementazione
public SetTransient(string value)
{
callMeTransient = value;
}
// altra implementazione
}
- Implementazione classe Scoped:
class ScopedImplementation: IScoped
{
var callMeScoped = ""
//altra implementazione
public SetScoped(string value)
{
callMeScoped = value;
}
//altra implementazione
}
Registriamoci (ConfigureServices
) con l’iniezione di dipendenze (DI) per decidere il ciclo di vita di ciascuna istanza del servizio:
services.AddSingleton<ISingleton, SingletonImplementation>();
services.AddTransient<ITransient , TransientImplementation>();
services.AddScoped<IScoped , ScopedImplementation>();
Utilizziamo/chiamiamo questi servizi da 3 classi differenti (ClassA
, ClassB
, e ClassC
) per capire il ciclo di vita di ciascuno di essi:
ClassA
:
public class ClassA
{
private ISingleton _singleton;
//costruttore per istanziare 3 servizi diversi che creiamo
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");
}
//altre implementazioni
}
ClassB
:
public class ClassB
{
private ISingleton _singleton;
//costruttore per istanziare 3 servizi diversi che creiamo
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");
}
//altre implementazioni
}
ClassC
:
public class ClassC
{
private ISingleton _singleton;
//costruttore per istanziare 3 servizi diversi che creiamo
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");
}
//altre implementazioni
}
Analisi
Analizziamo i risultati e il comportamento di ciascuno dei cicli di vita indicate sopra:
Singleton
Tutte le classi (ClassA
, ClassB
, e ClassC
) useranno la stessa istanza singola della classe SingletonImplementation
attraverso tutto il ciclo di vita dell’applicazione. Ciò significa che le proprietà, i campi e le operazioni della classe SingletonImplementation
saranno condivise tra le istanze usate in tutte le classi chiamanti. Qualsiasi aggiornamento alle proprietà o ai campi sovrascriverà le modifiche precedenti.
Le classi ClassA
, ClassB
e ClassC
stanno utilizzando il servizio SingletonImplementation
come istanza singleton e stanno chiamando SetSingleton
per aggiornare la variabile callMeSingleton
. In questo caso, ci sarà un singolo valore della variabile callMeSingleton
per tutte le richieste che cercano di accedere a questa proprietà. Qualsiasi classe lo modifichi per ultimo avrà il valore sovrascritto per callMeSingleton
.
ClassA
– Avrà la sua stessa istanza come altre classi per il servizioTransientImplementation
.ClassB
– Avrà la sua stessa istanza come altre classi per il servizioTransientImplementation
.ClassC
– Avrà la sua stessa istanza come altre classi per il servizioTransientImplementation
.
ClassA
, ClassB
e ClassC
stanno aggiornando la stessa istanza della classe SingletonImplementation
, che sovrascriverà il valore di callMeSingleton
. Pertanto, fai attenzione quando imposti o aggiorni le proprietà nell’implementazione del servizio singleton.
I servizi singleton vengono deallocati alla fine dell’applicazione (cioè al riavvio dell’applicazione).
Transient
Tutte le classi (ClassA
, ClassB
e ClassC
) useranno le loro singole istanze della classe TransientImplementation
. Ciò significa che se una classe richiede proprietà, campi o operazioni della classe TransientImplementation
, aggiornerà o sovrascriverà solo i valori delle sue singole istanze. Aggiornamenti alle proprietà o ai campi non sono condivisi con altre istanze di TransientImplementation
.
Capiremo:
ClassA
– Avrà la sua istanza del servizio diTransientImplementation
.ClassB
– Avrà la sua istanza del servizio diTransientImplementation
.ClassC
– Avrà la sua istanza del servizio diTransientImplementation
.
Immaginiamo di avere una ClassD
che chiama il servizio transiente dalle istanze di ClassA
, ClassB
e ClassC
. In questo caso, ogni istanza di classe sarebbe trattata come una diversa/sepatare istanza e ogni classe avrebbe il suo valore di callMeTransient
. Leggere i commenti in linea sotto per ClassD
:
public ClassD
{
// altra implementazione
// La riga di codice seguente aggiornerà il valore di callMeTransient a "Sono da ClassA" solo per l'istanza di ClassA.
// E non verrà modificato da eventuali chiamate successive dalla Classe B o classe B
ClassA.UpdateTransientFromClassA();
// La riga di codice seguente aggiornerà il valore di callMeTransient a "Sono da ClassB" solo per l'istanza di ClassB.
// E non sovrascriverà il valore per l'istanza di classA né verrà modificato da chiamate successive dalla Classe C
ClassB.UpdateTransientFromClassB();
// La riga di codice seguente aggiornerà il valore di callMeTransient a "Sono da ClassC" solo per l'istanza di ClassC.
// E non sovrascriverà il valore per le istanze di classA e classB né verrà modificato da eventuali chiamate successive da qualsiasi altra classe.
ClassC.UpdateTransientFromClassC();
// altra implementazione
}
I servizi transitori vengono smaltiti al termine di ogni richiesta. Usa Transient quando vuoi un comportamento senza stato all’interno della richiesta.
Scoped
Tutte le classi (ClassA
, ClassB
e ClassC
) utilizzeranno istanze singole della classe ScopedImplementation
per ciascuna richiesta. Ciò significa che le chiamate per proprietà/campi/operazioni sulla classe ScopedImplementation
avverranno su un’istanza singola nel contesto della richiesta. Qualsiasi aggiornamento delle proprietà/campi sarà condiviso tra le altre classi.
Capire meglio:
ClassA
– Avrà la sua istanza di servizio diTransientImplementation
.ClassB
– Avrà la stessa istanza del servizioTransientImplementation
diClassA
.ClassC
– Avrà la stessa istanza del servizioTransientImplementation
diClassA
eClassB
.
Supponiamo che tu abbia una ClassD
che chiama il servizio a scopo da istanze di ClassA
, ClassB
e ClassC
. In questo caso, ogni classe avrà un’istanza singola della classe ScopedImplementation
. Leggi i commenti in linea per ClassD
qui sotto.
public class ClassD
{
// altra implementazione
// Il codice sottostante aggiorna il valore di callMeScoped a "Io sono di classe A" per l'istanza di ClasseA
// Ma poiché è un ciclo di vita Scoped, mantiene un'unica istanza di ImplementazioneScoped
// Poi può essere sovrascritto dalla prossima chiamata di ClasseB o ClasseC
ClassA.UpdateScopedFromClassA();
// Il codice sottostante aggiorna il valore di callMeScoped a "Io sono di classe B" per l'istanza singola di ImplementazioneScoped
// E sovrascriverà il valore di callMeScoped anche per l'istanza di ClasseA.
ClassB.UpdateScopedFromClassB();
// Ora, se Classe A eseguirà qualunque operazione sulla ImplementazioneScoped,
// userà le ultime proprietà/valori campo che sono stati sovrascritti da Classe B.
// Il codice sottostante aggiorna il valore di callMeScoped a "Io sono di classe C"
// E sovrascriverà il valore di callMeScoped anche per le istanze di ClasseA e ClasseB.
ClassC.UpdateScopedFromClassC();
// Ora, se Classe B o Classe A eseguiranno qualunque operazione sulla ImplementazioneScoped, useranno le ultime proprietà/valori campo che sono stati sovrascritti da Classe C.
// altra implementazione
}
I servizi Scoped vengono disposti alla fine di ogni richiesta. Utilizzare Scoped quando si desidera un comportamento stateless tra richieste individuali.
Tempo Trivia
Il ciclo di vita di un servizio può essere sovrascritto da un servizio genitore in cui viene inizializzato. Confuso? Permettetemi di spiegare:
Prendiamo lo stesso esempio dalle classi precedenti e inizializziamo i servizi Transient e Scoped da SingletonImplementation
(che è un singleton) come illustrato qui sotto. questo avvierà i servizi ITransient
e IScoped
e sovrascriverà il ciclo di vita di questi per avere un ciclo di vita singleton come servizio genitore. In questo caso, il tuo applicativo non avrà alcun servizio Transient o Scoped (considerando che hai solo questi 3 servizi che abbiamo usato negli esempi).
Leggi le righe nel codice seguente:
public class SingletonImplementation: ISingleton
{
// costruttore per aggiungere l'inizializzazione dei servizi.
private readonly ITransient _transient
private readonly IScoped _scoped
SingletonImplementation(ITransient transient, IScoped scoped)
{
_transient = transient;
// Ora _transient sarebbe un servizio singleton indipendentemente da come è stato registrato come Transient
_scoped = scoped;
// ora scoped sarebbe un servizio singleton indipendentemente da come è stato registrato come Scoped
}
var callMeSingleton = ""
// altra implementazione
}
Riepilogo
Spero che l’articolo precedente sia utile per capire il topic. Ti consiglio di provare da te stesso con il contesto fornito sopra e non sarai mai più confuso. Singleton è il più semplice da capire perché una volta creata la sua istanza, sarà condivisa attraverso le applicazioni durante il ciclo di vita dell’applicativo. Sulla stessa linea del Singleton, le istanze Scoped imitano lo stesso comportamento ma solo durante il ciclo di vita di una richiesta attraverso l’applicativo. Transient è totalmente stateless, per ogni richiesta e per ogni istanza di classe verrà tenuto il suo proprio istanza del servizio.
Source:
https://dzone.com/articles/understanding-the-dependency-injection-lifecycle