La Lazy Initialization รจ una tecnica di programmazione che consiste nellโassegnare un valore a un oggetto solo quando รจ effettivamente necessario, ritardando la sua inizializzazione fino al momento in cui viene effettivamente utilizzato. Lo scopo รจ principalmente ridurre lo startup time di un programma (qualora si tratti di oggetti da creare sempre) o, in generale, ridurre il consumo di memoria quando si lavora con oggetti di grandi dimensioni o costosi in termini di risorse qualora questi non servono in tutti i casi. La memoria inoltre viene effettivamente risparmiata nel caso in cui lโoggetto non venga mai utilizzato, altrimenti il consumo vi รจ allo stesso modo ma โritardatoโ.
Vantaggi
- Riduzione del consumo di memoria: Non vengono allocate risorse per gli oggetti non utilizzati, risparmiando memoria.
- Miglioramento delle prestazioni: Lโallocazione e lโinizializzazione degli oggetti avviene solo quando รจ effettivamente necessario, riducendo il tempo di avvio dellโapplicazione.
- Scalabilitร : La Lazy Initialization puรฒ migliorare la scalabilitร di unโapplicazione, in quanto riduce il carico iniziale di risorse.
Limiti
- Overhead aggiuntivo: La classe
Lazy<T>
aggiunge un livello di astrazione, il che potrebbe causare un lieve overhead di prestazioni. - Performance: chiamare la property
.Value
di un oggettoLazy
รจ comunque piรน lento di chiamare lโoggetto stesso - Complessitร del codice: La Lazy Initialization puรฒ rendere il codice piรน complesso e difficile da gestire, in particolare se รจ necessario garantire la sincronizzazione tra thread.
- Non adatto per scenari multi-thread: Sebbene la classe
Lazy<T>
supporti la sincronizzazione dei thread, potrebbe essere meno efficiente rispetto allโinizializzazione anticipata in scenari multi-thread ad alta concorrenza. - Potenziali problemi di temporizzazione: La Lazy Initialization puรฒ causare problemi di temporizzazione se un oggetto viene inizializzato in un momento inaspettato o critico dellโesecuzione dellโapplicazione.
- Difficoltร nel debugging: La Lazy Initialization puรฒ rendere il debugging piรน difficile, poichรฉ lโinizializzazione degli oggetti avviene solo in determinati momenti, e potrebbe essere complicato individuare eventuali errori o eccezioni legate allโinizializzazione.
Esempio
Immaginiamo di avere unโapplicazione che richiede lโaccesso a un database remoto per recuperare alcuni dati. Lโinizializzazione della connessione al database potrebbe richiedere tempo e risorse significativi, soprattutto se si considerano le operazioni di rete coinvolte. In una situazione in cui non si sa con certezza se lโapplicazione avrร effettivamente bisogno di accedere al database, lโutilizzo della classe Lazy puรฒ essere molto utile.
Inizializzare la connessione al database solo quando viene effettivamente richiesto potrebbe migliorare le prestazioni dellโapplicazione e ridurre lโuso delle risorse del sistema. Utilizzando la classe Lazy, possiamo ritardare lโinizializzazione della connessione fino al momento in cui viene richiesta la prima operazione di accesso al database. In questo modo, se lโapplicazione non ha bisogno di accedere al database durante lโesecuzione, non sarร mai creata una connessione e non verranno sprecate risorse di sistema.
C#
In C#, รจ possibile utilizzare la classe Lazy<T>
del namespace System
per implementare la Lazy Initialization.
Il metodo principale รจ la property Value
che permette di inizializzare lโoggetto, se non รจ mai stato fatto, oppure ritornare lโistanza se รจ giร stata costruita in precedenza.
Lโutilizzo รจ molto semplice:
// Utilizzo di Lazy Initialization per l'oggetto 'MyExpensiveObject'
Lazy<MyExpensiveObject> lazyObject = new Lazy<MyExpensiveObject>();
MyExpensiveObject expensiveObject = lazyObject.Value;
La classe fornisce anche un costruttore per creare lโoggetto tramite factory new Lazy<T>(Func<T> valueFactory)
e una property booleana IsValueCreated
per sapere se รจ stato mai costruito lโoggetto.
Multi-threading
La classe Lazy permette di configurarne il comportamento di creazione qualora venga utilizzati in scenari multi-thread. Queste configurazioni influiscono solo sulla creazione, lโaccesso alla variabile interna รจ invece sempre thread-safe.
isThreadSafe
A costruttore posso passare il parametro isThreadSafe
che, se impostato a true, circonda di un lock la creazione dellโoggetto.
Vediamo esplicitamente i due casi:
isThreadSafe: false
: il metodo per creare il valore creerร semplicemente il valore, lo imposterร nella memoria interna e restituirร il valore.isThreadSafe: true
: la creazione verrร racchiusa allโinterno di unlock
, impedendo a piรน di un thread di creare lโoggetto.
LazyThreadSafetyMode Enum
A costruttore posso definire il comportamento della classe Lazy
in scenari multi-thread tramite lโenum LazyThreadSafetyMode
.
Questo puรฒ avere tre modalitร :
None
: Indica che non sono previste misure di sicurezza del thread. Questa modalitร รจ la meno costosa in termini di prestazioni, ma non รจ thread-safe. Questo รจ il comportamento di default.PublicationOnly
: Con questa modalitร non ho lock sulla creazione dellโoggetto, conseguentemente ci puรฒ essere la creazione simultanea di piรน di un valore interno ma poi la classe utilizzerร unInterlocked.CompareExchange
internamente per assicurarsi che il valore del primo oggetto creato completamente sia quello utilizzato per lโoggetto.ExecutionAndPublication
: Indica che la creazione e lโinizializzazione dellโoggetto sono thread-safe (circondata dalock
, analogo aisThreadSafe=true
).
Esempio
In questo esempio, lโoggetto MyExpensiveObject
viene inizializzato (quindi ne viene chiamato il costruttore) solo quando si accede alla proprietร Value
dellโoggetto lazyObject
. Successivamente, il valore viene messo in una cache allโinterno dellโoggetto Lazy<T>
stesso in modo che, nelle chiamate successive alla proprietร .Value
, verrร restituito il valore memorizzato senza ulteriori chiamate al costruttore.
Questo permette di ridurre la memoria utilizzata sullo Heap, in quanto lโoggetto MyExpensiveObject
verrร allocato e inizializzato solo quando รจ effettivamente necessario.
// Utilizzo di Lazy Initialization per l'oggetto 'MyExpensiveObject'
Lazy<MyExpensiveObject> lazyObject = new Lazy<MyExpensiveObject>();
Console.WriteLine("Lazy object creato.");
// L'oggetto verrร inizializzato solo quando lo richiediamo esplicitamente
Console.WriteLine("Premi un tasto per inizializzare l'oggetto.");
Console.ReadKey();
MyExpensiveObject expensiveObject = lazyObject.Value;
Console.WriteLine("Oggetto inizializzato e pronto per l'uso.");
Console.WriteLine("Premi un tasto per accedere nuovamente all'oggetto.");
Console.ReadKey();
MyExpensiveObject expensiveObject2 = lazyObject.Value;
Console.WriteLine("Oggetto giร inizializzato, nessuna nuova inizializzazione.\n");
class MyExpensiveObject
{
public MyExpensiveObject()
{
Console.WriteLine("Inizializzazione dell'oggetto MyExpensiveObject...");
// Simulazione di un'operazione costosa
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Inizializzazione completata.");
}
}
Esempio 2
In questo caso utilizzo la classe Lazy per definire una Regex come field ma senza occupare la memoria nellโinizializzazione, qualora esistano scenari in cui questa non mi serve.
private static Lazy<Regex> colorBlockRegEx = new Lazy<Regex>(
()=> new Regex("VERY_COMPLEX_REGEX"));
Test completo
I file di test si trovano in RowandishAwesomeWiki
progetto Various
e classe LazyTest
. Vi sono anche dei benchmark con la classe LazyMemoryBenchmark
.