Le novità di Visual Basic 9.0 - Parte 1
a cura di Antonio Catucci (requisiti: intermedio)

Premessa
Ebbene, ci siamo. Con l'uscita della Beta 2 del Framework 3.5 possiamo, con ragionevole certezza, affermare di essere ormai prossimi a quella che sarà la nuova versione del .Net Framework per il 2008.

Le novità sono tante e molto interessanti, ma noi ci focalizzeremo su quello che di nuovo ci offrirà Visual Basic 2008.
Va detto che queste novità, a parte la loro utilità intrinseca, sono importanti per capire ed usare correttamente LINQ.

Local Type Inference
La prima novità è la possibilità di non specificare esplicitamente il tipo di una variabile, ma di lasciarlo determinare a runtime dal valore per inferenza.
Ad esempio, il codice:

    Dim x = 5
    Dim s = "stringa"
    Dim files = System.IO.Directory.GetFiles("c:\")

dichiara tre variabili locali di tipo, rispettivamente, Integer, String e array di String, ed è equivalente a:

    Dim x As Integer = 5
    Dim s As String = "stringa"
    Dim files As String() = System.IO.Directory.GetFiles("c:\")

E' importante sottolineare il fatto che si tratta di variabili fortemente tipizzate e non di tipo Object, infatti l'Intellisense riconosce correttamente il tipo segnalando eventuali errori come l'assegnazione di un tipo diverso.
Oltre che nelle dichiarazioni è possibile sfruttare questa funzionalità anche nei costrutti iterativi che usano una variabile contatore:

    ' i non è dichiarata esplicitamente
    For i = 1 To 10
      '...
    Next

Oppure:

    For Each f In files
      Console.WriteLine(f)
    Next

Questa nuova funzionalità è attiva di default in VB (solo per i nuovi progetti, non per quelli convertiti) ed è modificabile da codice con la nuova opzione:

Option Infer On|Off

Se si disattiva questa funzionalità, bisognerà dichiarare le variabili come succede con il FW 2.0 indicando obbligatoriamente il tipo con As.
Esistono, però, delle limitazioni nell'uso: è utilizzabile solo per variabili locali dichiarate con Dim e non supporta variabili statiche.

Object Initializer
Un'altra novità sono gli inizializzatori di oggetti, ovvero la possibilità di valorizzare proprietà e campi pubblici di un oggetto in fase di dichiarazione mediante la parola chiave With. Per capire bene come funziona ipotizziamo di avere una classe di questo tipo:

Public Class Person
  Sub New()
  End Sub
  ' Per praticità solo campi e non properties complete
  Public Name As String
  Public LastName As String
  Public Age As Integer
End Class

Attualmente è possibile istanziare e valorizzare la classe in un solo modo:

    Dim p As New Person
    With p
      .Name = "Antonio"
      .LastName = "Catucci"
      .Age = 30
    End With

In VB2008 è possibile abbreviare il tutto in una sola riga di codice:

    Dim p As New Person With {.Name = "Antonio", _
                              .LastName = "Catucci", _
                              .Age = 30}

Le due istruzioni sono equivalenti.
Grazie a questa caratteristica è possibile inizializzare un oggetto in assenza di costruttori specifici (come nell'esempio) oppure di combinare le due cose:

    ' Ipotizzando un costruttore New(name, lastName)
    Dim p As New Person("Antonio", "Catucci") With {.Age = 30}

Volendo, è possibile ridurre ancora l'istruzione usando l'inferenza:

    Dim p = New Person("Antonio", "Catucci") With {.Age = 30}

Naturalmente, l'Intellisense di VB aiuta notevolmente durante la scrittura del codice:


Figura 1 - Supporto dell'Intellisense con gli inizializzatori.

E' possibile nidificare le inizializzazioni qualora ci sia una proprietà complessa.
Sempre considerando la classe Person, se ci fosse una proprietà Wife di tipo Person avremmo:

    Dim p = New Person With {.Name = "Antonio", _
                             .LastName = "Catucci", _
                             .Age = 30, _
                             .Wife = New Person With {.Name = "Megane", _
                                                      .LastName = "Fox", _
                                                      .Age = 22} _
                            }

Un ultimo esempio d'uso, combinando sia i type inference che gli inizializzatori, è il seguente:

    Dim Family = New Person() {New Person With {.Name = "Antonio"}, _
                               New Person With {.Name = "Megane"}, _
                               New Person With {.Name = "Alessandro"}, _
                               New Person With {.Name = "Alice"} _
                              }

Dove Family è un array di 4 elementi di tipo Person.

Anonymous Type
Una novità importante, soprattutto per LINQ, è rappresentata dai tipi anonimi.
Come il nome stesso lascia intendere, si tratta di tipi senza nome, appunto, che è possibile creare a runtime ed utilizzarli come un normale oggetto.
Ad esempio, il tipo Person usato negli esempi precedenti può essere creato in questo modo:

    Dim person = New With {.Name = "Antonio", _
                           .LastName = "Catucci", _
                           .Age = 30}

senza avere nessuna classe dichiarata e si tratta di un oggetto come gli altri:


Figura 2 - un tipo anonimo

Notate l'assenza sia della clausola As che del nome del tipo dopo New.
Trattandosi di un tipo anonimo, non occorre specificare il nome ed il tipo Person della variabile person viene dedotto per inferenza. Se proviamo a stampare il nome del tipo della variabile Person mediante il metodo GetType, otterremo un risultato del genere:

? person.GetType().ToString
"VB$AnonymousType_0`3[System.String,System.String,System.Int32]"

La prima parte è il nome che il compilatore ha assegnato al nostro oggetto e varia (o potrebbe variare) ad ogni compilazione, mentre tra parentesi quadre troviamo l'elenco dei tipi di ciascun membro definito (nel nostro caso due String e un Int32, dedotti per inferenza).
Quello mostrato nell'esempio, benché sia il più comune, non è l'unico modo per definire un tipo anonimo: è possibile, infatti, definire una proprietà mediante una espressione o usando altri tipi. Ad esempio:

    Dim name = "Antonio", lastName = "Catucci", age = 29
    Dim p = New With {name, lastName, age, _
                      .Wife = New Person With {.Name = "Megane", _
                                               .LastName = "Fox", _
                                               .Age = 22} _
                     }

Vediamo quali sono le caratteristiche di questi oggetti "anonimi".
I membri sono definiti come Property Get/Set ed è obbligatorio che ce ne sia almeno una. E' possibile tuttavia avere proprietà ReadOnly specificando la chiave Key prima della proprietà.
Ad esempio:

    Dim person = New With {.Name = "Antonio", _
                           .LastName = "Catucci", _
                           Key .Age = 30}

crea una classe la cui proprietà Age è ReadOnly.
L'uso di Key è importante anche per un altro motivo, ovvero per il confronto tra tipi anonimi. L'oggetto generato, infatti, implementa il metodo Equals() solo se esiste almeno una proprietà marcata con la parola chiave Key e solo queste proprietà sono utilizzate per il confronto.
Ad esempio:

    Dim p1 = New With {.Name = "Antonio", .LastName = "Catucci", .Age = 30}
    Dim p2 = New With {Key .Name = "Antonio", .LastName = "Catucci", .Age = 30}
    Dim eq = p1.Equals(p2)

Il cui confronto (eq) avrà esito negativo perché p1 non ha nessuna proprietà Key.
Questi due oggetti invece sono uguali, nonostante abbiano diversi valori per la proprietà Age, perché il confronto viene fatto solo attraverso la proprietà Name:

    Dim p1 = New With {Key .Name = "Antonio", .LastName = "Catucci", .Age = 30}
    Dim p2 = New With {Key .Name = "Antonio", .LastName = "Catucci", .Age = 29}

Infine, le proprietà Key vengono utilizzate per generare il codice hash dell'oggetto, restituito dal metodo GetHashCode().
Oltre ai metodi Equals e GetHashcode, viene fatto l'override del metodo ToString restituendo l'elenco concatenato delle proprietà con i rispettivi valori:

? p2.ToString
"{ Name = Antonio, LastName = Catucci, Age = 29 }"

Purtroppo non c'è modo di modificare l'implementazione di questo metodo.

Vediamo, ora, quali sono gli usi consentiti e le limitazioni.
Un oggetto anonimo è una classe interna (Friend) generata dal compilatore il cui nome non è noto a priori. Inoltre la visibilità di una variabile "anonima" è locale alla funzione in cui è definita. Questo non vuol dire che non si possa avere una variabile anonima a livello di classe, ma il prezzo da pagare è abilitare il late binding con Option Strict Off. In questo modo è possibile usare oggetti anonimi passati come parametri di tipo Object. Ad esempio:

Option Strict Off
Public Class Form1

  Public Sub MySub(ByVal value As Object)
    Console.WriteLine(value.Name)
  End Sub

  Private Sub AltraSub()
    MySub(New With {Key .Name = "Antonio", .LastName = "Catucci", .Age = 30})
  End Sub

funziona senza problemi, ma è evidente come il codice, non solo sia poco leggibile, ma anche soggetto ad errori (provate a pensare se cambiasse il nome di una proprietà...).

A mio parere è bene limitare al minimo l'uso di Option Strict Off, anche perché i tipi anonimi sono stati introdotti principalmente per essere usati con le Query Expressions.

Conclusione
Poiché le medicine van prese a piccole dosi, questo articolo termina qui.
Nel prossimo vedremo altre due importanti novità: Extension Methods e Lambda Expression.
In merito a quest'articolo, potete scrivere all'autore Antonio Catucci, del quale potete visitare il blog.