Passa ai contenuti principali

La classe Lazy(Of T)

Una interessante classe inserita nella versione 4.0 del framework è la Lazy(Of T).

Si tratta di un generic il cui scopo è quello di permettere la creazione di oggetti differita.

Uno scenario tipico che può capitare quando sviluppiamo è quello di avere una nostra classe che occupa molte risorse (memoria, accesso a database, accesso a filesystem, e via discorrendo) e desiderare che tale classe venga istanziata solo quando serve.

In più, alcune volte, sarebbe anche comodo poter avere un accesso concorrente a tale istanza.

Se il primo requisito è facilmente implementabile con un manciata di righe di codice, il secondo non è altrettanto banale.

La classe Lazy(Of T) ci viene in aiuto perché è, di fatto, un wrapper che gestisce la creazione dell’istanza del tipo T e il thread safe in maniera nativa.

La classe Lazy prevede una serie di costruttori il più semplice dei quali non prevede argomenti. Sia MyClass la nostra classe di cui vogliamo gestire la creazione differita:

  1. Public Class [MyClass]
  2.  
  3.     Public Sub New()
  4.         ConsoleHelper.WriteConsole("MyClass - New")
  5.     End Sub
  6.  
  7.     Public Sub New(i As Integer)
  8.         ConsoleHelper.WriteConsole(String.Format("MyClass - New with {0}", i))
  9.     End Sub
  10.  
  11.     Public Sub MyMethod()
  12.         ConsoleHelper.WriteConsole("MyClass - MyMethod")
  13.     End Sub
  14. End Class

La classe ConsoleHelper permette di utilizzare la Console per scrivere dei messaggi a cui viene anteposto un timestamp. Nei costruttori della classe MyClass ho inserito la scrittura dei messaggi per poter, effettivamente, capire quando il costruttore stesso viene invocato.

Prendiamo in esame il seguente pezzo di codice:

  1. Sub Main()
  2.     ConsoleHelper.WriteConsole("CreateSimpleLazy - Before")
  3.     Dim lazy = CreateSimpleLazy()
  4.     ConsoleHelper.WriteConsole("CreateSimpleLazy - After")
  5.     Threading.Thread.Sleep(1000)
  6.     lazy.Value.MyMethod()
  7.     Console.ReadLine()
  8. End Sub

e il seguente:

  1. Public Function CreateSimpleLazy() As Lazy(Of [MyClass])
  2.     Dim retVal = New Lazy(Of [MyClass])
  3.  
  4.     Return retVal
  5. End Function

Il metodo CreateSimpleLazy altro non fa che istanziare un oggetto di classe Lazy(Of MyClass) e restituirlo al chiamante.

Nel metodo chiamante eseguiamo la chiamata al metodo MyMethod della MyClass in maniera differita (cioè solo dopo aver atteso 1 secondo dall’istanziazione dell’oggetto Lazy).

Il risultato a video è il seguente:

SNAGHTML122e0a4b

Come possiamo osservare il costruttore della classe MyClass (riga “MyClass – new”) viene richiamato solo nel momento in cui si esegue l’istruzione lazy.Value.MyMethod e non quando viene creato l’oggetto Lazy.

In effetti la classe Lazy espone l’istanza della classe che wrappa tramite la proprietà Value all’interno della quale viene gestito l’effettivo richiamo del costruttore. Se l’oggetto wrappato non è stato ancora istanziato, nel momento in cui viene utilizzata la proprietà Value, viene richiamato il costruttore di default della classe wrappata.

Ma se la nostra classe non prevede un costruttore di default?

In questo caso ci viene in aiuto un altro costruttore della Lazy la cui sintassi è la seguente:

  1. Public Sub New(
  2.               valueFactory As Func(Of T)
  3.               )

Questo costruttore prevede che si possa utilizzare una Func(Of T) come parametro (ad esempio una lambda expression), all’interno della quale creeremo l’istanza della nostra classe e la restituiremo alla Lazy che la gestirà in maniera del tutto analoga al caso precedente:

  1. Sub Main()
  2.     ConsoleHelper.WriteConsole("CreateParametricLazy - Before")
  3.     Dim parametricLazy = CreateParametricLazy(100)
  4.     ConsoleHelper.WriteConsole("CreateParametricLazy - After")
  5.     Threading.Thread.Sleep(1000)
  6.     parametricLazy.Value.MyMethod()
  7.  
  8.     Console.ReadLine()
  9. End Sub

In questo caso utilizziamo il costruttore della classe MyClass che prevede un parametro intero:

  1. Public Function CreateParametricLazy(i As Integer) As Lazy(Of [MyClass])
  2.     Dim retVal = New Lazy(Of [MyClass])(Function()
  3.                                             Return New [MyClass](i)
  4.                                         End Function)
  5.  
  6.     Return retVal
  7. End Function

Il risultato che otteniamo è:

SNAGHTML1236d622

La classe Lazy, per default, ThreadSafe cioè permette accesso contemporaneo (e sicuro) alla proprietà Value da thread differenti. La modalità di default è la LazyThreadSafetyMode .ExecutionAndPubblication che prevede che un solo thread possa eseguire l’inizializzazione di T ma che tale istanza sia poi disponibili a tutti i thread che la richiedono.
Questo significa che se più thread tentano di eseguire l’inizializzazione della classe T, uno solo di questi ha accesso al costruttori e gli altri attendono l’effettiva creazione della stessa.

Questo fatto comporta che se la nostra classe utilizza dei meccanismi di thread safe interni e questi non lavorano correttamente, si potrebbero creare dei dedlock da cui non si può uscire.

Possiamo stabilire il comportamento della classe Lazy rispetto all’accesso contemporaneo con opportuni costruttori della stessa che ci permettono di avere un’istanza non ThreadSafe oppure di stabilire esattamente il LazyThreadSafetyMode da utilizzare. I possibili valori di questa enumerazione sono:

  • None : non thread safe;
  • ExecutionAndPubblication: un unico thread può inizializzare T la cui istanza viene utilizzata da tutti i thread. E’ una modalità ThreadSafe;
  • PublicationOnly : più thread possono inizializzare T, il primo che riesce rende l’istanza disponibile e viene utilizzata dagli altri. Eventuali altre istanze vengono scartate. E’ una modalità ThreadSafe.

La tipologia di ThreadSafe scelta influenza anche il comportamento della classe Lazy rispetto alle eventuali eccezioni generate dal costruttore della classe contenuta.

Se stiamo utilizzando il costruttore di default, l’eventuale eccezione generata viene immediatamente sollevata (a prescindere da quale valore di LazyThreadSafetyMode  abbiamo scelto) mentre se stiamo utilizzando un costruttore con parametri l’eventuale eccezione generata si comporta in maniera differente in base al valore di LazyThreadSafetyMode scelto.

Se si è scelto PublicationOnly (valore di default) le eccezioni vengono immediatamente generate nel costruttore della Lazy (come nel caso precedente), mentre se stiamo utilizzando uno delle altre sue modalità di LazyThreadSafetyMode, allora le eccezioni vengono salvate in cache e proposte nel momento in cui viene invocata la proprietà Value.

Infine, la classe Lazy(Of T) prevede la proprietà IsValueCreated che permette di conoscere se l’istanza di T è stata già creata o meno.

 

Commenti

Post popolari in questo blog

VB.NET : Aggregare stringhe con LINQ

Tip facile facile, ma a qualcuno potrebbe servire. Supponiamo di avere una lista di stringhe (magari come risultato di una query LINQ) e di voler ottenere una stringa con la concatenazione delle stesse: Dim list = CreateList() Dim concatStr = (From s In list _ Select s).Aggregate( Function (currentString, nextString) currentString + nextString) MessageBox.Show(concatStr) Il metodo CreateList non ci interessa, in questo momento, ma crea una lista di oggetti String. Protected Function CreateList() As IEnumerable( Of String ) Dim list As String () = {" stringa1 ", " stringa2 ", " stringa3 ", " stringa4 ", " stringa5 "} Return list.AsEnumerable() End Function Questo metodo potrebbe restituire una qualsiasi lista di oggetti di cui, nella select successiva recuperiamo solo stringhe. La stessa tecnica è utilizzabile per concatenare stringhe inserendovi un carattere separatore Dim list = CreateList() Dim

VB.NET: SplashScreen con effetto fade-in

In questo post vorrei proporvi un modo per realizzare una splash screen per le nostre applicazioni Windows Form che appare progressivamente con un effetto fade. Supponiamo di avere il nostro progetto VB.NET in una soluzione Visual Studio 2008 in cui abbiamo il sorgente della nostra applicazione Windows Form. Inseriamo una splash screen utilizzando il menù Progetto->Aggiungi Nuovo Elemento e selezionando il tipo di elemento “Schermata Iniziale” A questo punto Visual Studio creerà, automaticamente, la schermata iniziale che possiamo personalizzare graficamente come vogliamo. Per poter fare in modo che questa finestra appaia nel momento in cui avviamo l’applicazione, è necessario aprire le proprietà del progetto e impostare la maschera di avvio: In questo modo, all’avvio dell’applicazione, la schermata appare immediatamente e scompare un attimo prima della visualizzazione della finestra dell’applicazione. Possiamo far apparire la schermata iniziale con un ef

VB.NET: Convertire un file DOC in RTF e PDF con office interop

In questo post vorrei proporvi del codice per poter convertire un file .doc in un file .rtf oppure .pdf utilizzando le API di interoperabilità di Office. Creeremo una classe, DocConverter, che esporrà le due funzionalità sopra citate. Cominciamo con il prevedere un attributo privato della classe che rappresenterà l’applicazione Word che utilizzeremo per la conversione. Creeremo l’istanza dell’attributo privato all’interno del costruttore della classe: Public Sub New () If Not CreateWordApp() Then Throw New ApplicationException(" Assembly di interoperabilità con Office non trovato! ") End If End Sub Private _wordApp As Word.ApplicationClass Protected Function CreateWordApp() As Boolean Dim retval = True Try _wordApp = New Word.ApplicationClass() _wordApp.Visible = False Catch ex As System.Exception _wordApp = Nothing retval = False End Try Return retval End Function La conve