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

  1. Riduzione del consumo di memoria: Non vengono allocate risorse per gli oggetti non utilizzati, risparmiando memoria.
  2. Miglioramento delle prestazioni: Lโ€™allocazione e lโ€™inizializzazione degli oggetti avviene solo quando รจ effettivamente necessario, riducendo il tempo di avvio dellโ€™applicazione.
  3. Scalabilitร : La Lazy Initialization puรฒ migliorare la scalabilitร  di unโ€™applicazione, in quanto riduce il carico iniziale di risorse.

Limiti

  1. Overhead aggiuntivo: La classe Lazy<T> aggiunge un livello di astrazione, il che potrebbe causare un lieve overhead di prestazioni.
  2. Performance: chiamare la property .Value di un oggetto Lazy รจ comunque piรน lento di chiamare lโ€™oggetto stesso
  3. 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.
  4. 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.
  5. 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.
  6. 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 un lock, 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ร :

  1. 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.
  2. 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ร  un Interlocked.CompareExchange internamente per assicurarsi che il valore del primo oggetto creato completamente sia quello utilizzato per lโ€™oggetto.
  3. ExecutionAndPublication: Indica che la creazione e lโ€™inizializzazione dellโ€™oggetto sono thread-safe (circondata da lock, analogo a isThreadSafe=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.