Le novità di VB.NET 10 al microscopio
Scritto da
Massimo Bonanni
il
giovedì 3 giugno 2010
Linguaggio:
•
Framework:
•
Livello: 100
La versione 10 del linguaggio Visual Basic.NET introduce alcune
novità che lo avvicinano sempre di più al cugino C#. In questo
articolo vedremo, in dettaglio, alcune di queste novità
confrontandole con quello che il linguaggio ci metteva a
disposizione nelle precedenti versioni.
Implicit Line Continuation
Gli sviluppatori VB.NET possono, finalmente, andare a capo nelle
proprie espressioni senza digitare il carattere "_".
Ad esempio possiamo scrivere:
Sub Main()
Dim arr = {1, 5, 6, 8, 2, 4, 1, 9, 3}
Dim query = From i In arr
Where i > 5
Select i
End Sub
Esistono, però, dei casi in cui non è possibile omettere il
carattere "_". In particolare il carattere "_" può essere omesso in
questi casi :
- Dopo un carattere ",";
Dopo una parentesi tonda aperta "(" o prima di una chiusa ")";
- Dopo una parentesi graffa aperta "{" o prima di una chiusa
"}";
- Dopo un tag <%= o prima di un tag %> all'interno di un
XML literal;
- Dopo l'operatore di concatenazione &;
- Dopo uno degli operatori di assegnazione =, &=, :=, +=, -=,
*=, /=, \=, ^=, <<=, >>=;
- Dopo uno degli operatori binari +, -, /, *, Mod, <>,
<, >, <=, >=, ^, >>, <<, And, AndAlso, Or,
OrElse, Like, Xor all'interno di un espressione;
- Dopo l'operatore Is o l'operatore IsNot;
- Dopo il carattere "." che divide il nome di una variabile da il
nome di un membro della classe a meno che non ci si trovi
all'interno di una sintassi With…End With;
- Dopo i qualificatori delle proprietà XML ".", ".@" e "…" a meno
che non ci si trovi all'interno di un costrutto With…End With;
- Dopo il simbolo < o prima del simbolo > nella definizione
di un attributo o dopo il carattere > che separa l'attributo
dall'entità a cui è associato. Deve essere aggiunto il carattere
"_" per gli attributi di livello Assembly o Module;
- Prima o dopo uno degli operatori di una query (Aggregate,
Distinct, From, Group By, Group Join, Join, Let, Order By, Select,
Skip, Skip While, Take, Take While, Where, In, Into, On, Ascending,
and Descending);
- Dopo la parola chiave In di un For Each;
- Dopo la parola chiave From nell'inizializzazione di una
collection.
Definizione compatta delle proprietà
Una interessante feature inserita in VB.NET 10 è quella (già
esistente nel linguaggio C# in Visual Studio 2008) di poter
definire in maniera compatta quelle proprietà di tipo "standard"
(cioè proprietà in lettura e scrittura con lo stesso identificatore
di accesso).
Nelle precedenti versioni di Visual Studio, per definire una
proprietà, avremmo dovuto scrivere:
Private _FirstName As String
Public Property FirstName As String
Get
Return Me._FirstName
End Get
Set(ByVal value As String)
Me._FirstName = value
End Set
End Property
Nella nuova versione di VB.NET, invece, possiamo scrivere:
Public Property FirstName As String
con un notevole risparmio di tempo e leggibilità del codice.
Ma cosa succede dietro le quinte nel momento in cui compiliamo
una classe con le proprietà definite in questo modo?
Supponiamo di avere la semplice classe:
Public Class Person
Public Property FirstName As String
Public Property LastName As String
End Class
Se apriamo l'assembly generato dalla compilazione con uno
strumento che ci consente di esaminare il codice compilato come, ad
esempio Reflector di Red Gate (http://www.red-gate.com/products/reflector/index.htm),
osserviamo:

Come possiamo vedere, il compilatore ha generato la proprietà in
maniera molto simile a come l'avremmo implementata noi nella
precedente versione del linguaggio.
Unica differenza è la presenza degli attributi:
- CompilerGenerated: indica il fatto che
l'attributo privato è stato generato dal compilatore e non scritto
dall'utente;
- DebuggerBrowsable: indica se e come il membro
a cui è agganciato può essere visualizzato nella finestra delle
variabili del debugger. Nel caso specifico lo stato e Never il che
comporta che se siamo in debug e utilizziamo il QuickWatch non
vediamo l'attributo;
- DebuggerNonUserCode: indica che il pezzo di
codice che decora (nel caso specifico i setter e getter delle
property) non fanno parte del codice scritto dall'utente e che,
quindi, non deve essere visibile allo stesso utente quando si
esegue il debug. In questo caso, ad esempio, se in debug si tenta
di eseguire un'operazione di Step Into all'interno dei setter o dei
getter, l'ambiente di sviluppo eseguirà automaticamente
un'operazione di steps through.
Tutti e tre gli attributi sono presenti nel framework fin dalla
versione 2.0.
Array Literals
La sintassi degli array literals ci permette di definire,
istanziare e valorizzare array di oggetti in maniera rapida e
compatta.
Ad esempio prendiamo in esame la classe:
Public Class Repository
Public Function GetIntegerArray() As Integer()
Dim arr = {3, 7, 10, 12, 6, 2, 1}
Return arr
End Function
End Class
Osserviamo che l'array di Integer arr è stato definito, riempito
ed istanziato con una sola istruzione in maniera molto
compatta.
Il compilatore riesce ad applicare la local type inference
all'array così creato, infatti se posizioniamo il cursore al di
sopra della definizione di arr, otteniamo:

Vediamo come si comporta il compilatore dietro le quinte
analizzando il codice generato grazie a Reflector:

Come possiamo osservare, il compilatore non fa altro che
utilizzare la consueta sintassi per l'inizializzazione degli array
(come avremmo fatto in VB.NET 9).
Il compilatore è in grado, inoltre, di dedurre il tipo degli
oggetti componenti l'array andando a vedere quale è il tipo
"minimo" in grado di contenere tutti gli oggetti presenti
all'interno delle parentesi graffe.
Per questo motivo possiamo utilizzare anche oggetti di classi
differenti come, ad esempio:
Dim oggetti = {1, "a", True}
Se siamo in modalità Option Strict Off, il compilatore dedurrà
che la variabile oggetti è, di fatto, un array di Object (tipo
"minimo" in grado di contenere tutti gli elementi
dell'array).
In modalità Option Strict On otterremo un errore di compilazione ma
possiamo ancora utilizzare questo tipo di assegnazione in alcuni
casi particolari come, ad esempio, array tra integer, double, short
e simili.
Dim oggetti = {1, 2.5, CShort(2)}
Per concludere, osserviamo che questo modo di definire gli array
funziona anche per array costituiti da nostre classi:
Public Function GetCustomerArray() As Customer()
Dim arr = {New Customer() With {.Id = 1,
.FirstName = "Giuseppe",
.LastName = "Verdi"},
New Customer() With {.Id = 2,
.FirstName = "Carlo",
.LastName = "Rossi"},
New Customer() With {.Id = 3,
.FirstName = "Filippo",
.LastName = "Bianchi"}}
Return arr
End Function
Collection Initializers
Altra nuova funzionalità di linguaggio inserita nella versione
VB.NET 10 è la possibilità di definire, istanziare e riempire delle
collezioni di oggetti in un unica istruzione.
Nelle versioni precedenti di VB.NET avevamo bisogno di
scrivere:
Dim list = New List(Of Integer)
With list
.Add(1)
.Add(2)
.Add(3)
.
.
End With
Nella versione 10 di VB.NET possiamo, invece, scrivere
Dim list = New List(Of Integer) From {1,2,3,.,.,.,.}
I valori passati dopo la clausola From possono anche essere delle
variabili.
Ma come si comporta il compilatore quando utilizziamo la nuova
sintassi? Per verificare ciò, supponiamo di avere la seguente
classe:
Public Class CityRepository
Public Function GetCity() As List(Of String)
Dim cityList = New List(Of String) From
{"Roma",
"Milano",
"Napoli",
"Genova",
"Firenze",
"Torino"}
Return cityList
End Function
End Class
Se la compiliamo (inserendola in un progetto Visual Studio 2010) ed
analizziamo l'assembly ottenuto utilizzando Reflector
otteniamo:

Proviamo, ora a riscrivere la funzione precedente utilizzando la
vecchia sintassi:
Public Class CityRepository
Public Function GetCity2() As List(Of String)
Dim cityList = New List(Of String)
cityList.Add("Roma")
cityList.Add("Milano")
cityList.Add("Napoli")
cityList.Add("Genova")
cityList.Add("Firenze")
cityList.Add("Torino")
Return cityList
End Function
End Class
Otteniamo:

Il compilatore, quindi, se può cerca di riportare il nostro
codice nella versione più compatta ed efficiente.
Da ciò se ne potrebbe dedurre che scrivere nella nuova modalità
o nella vecchia è esattamente la stessa cosa e il solo vantaggio
che otteniamo è una migliore leggibilità del codice (e già sarebbe
sufficiente per utilizzarla).
In realtà non è esattamente così e la nuova sintassi ci
fornisce, incidentalmente, un altro piccolo vantaggio. Supponiamo
di avere la seguente classe:
Public Class IntegerRepository
Public Function GetList() As List(Of Integer)
Dim lista As List(Of Integer) = Nothing
Try
lista = New List(Of Integer)
With lista
.Add(2)
.Add(4)
.Add(System.Convert.ToInt32("a"))
.Add(6)
End With
Catch ex As Exception
End Try
Return lista
End Function
End Class
Viene sollevata un'eccezione nel momento in cui si cerca di
convertire la stringa "a" in un intero.
All'uscita della funzione, la lista è, però, parzialmente
valorizzata (sono infatti presenti i valori 2 e 4 ma non il 6).

La soluzione al problema è impostare a Nothing la lista
all'interno della clausola Catch della funzione
Se, ora modifichiamo la classe nel seguente modo:
Public Class IntegerRepository
Public Function GetListNew() As List(Of Integer)
Dim lista As List(Of Integer) = Nothing
Try
lista = New List(Of Integer) From
{2, 4, 6, System.Convert.ToInt32("a")}
Catch ex As Exception
End Try
Return lista
End Function
End Class
Otteniamo che la lista di ritorno, nonostante sia sempre sollevata
l'eccezione, è vuota (anzi non è neanche istanziata):

In sostanza, utilizzando la nuova sintassi si realizza una sorta
di "transazione" nell'operazione di
definizione/creazione/riempimento della lista: o abbiamo la lista
completa o niente.
Se mettiamo a confronto le due funzioni (rinominando in
GetListNew() quella creata nella nuova modalità) possiamo
analizzare il codice generato e verificare che il compilatore si
comporta in maniera completamente differente:

L'utilizzo dei collection inizializers, quindi, permette di
ottenere una sintassi compatta e più leggibile e, incidentalmente,
anche una sorta di transazionalità nell'operazione di creazione
della collection.
La sintassi dei collection initializers è applicabile anche a
collezioni di classi definite da noi. Ad esempio supponiamo di
definire una classe Customer:
Public Class Customer
Property ID As Integer
Property FirstName As String
Property LastName As String
End Class
e di dover creare una lista di customer:
Dim list As New List(Of Customer) From {
{New Customer() With {.ID = 1000,
.FirstName = "Mario",
.LastName = "Rossi"}},
{New Customer() With {.ID =1100,
.FirstName ="Giuseppe",
.LastName = "Verdi"}],
{New Customer() With {.ID =1200,
.FirstName ="Carlo",
.LastName = "Bianchi"}]}
Il codice che ne risulta comincia a diventare piuttosto poco
leggibile.
Per semplificare la scrittura del codice possiamo definire un
metodo di estensione Add() che agisce su un'istanza
dell'interfaccia IList e aggiunge un nuovo Customer:
Imports System.Runtime.CompilerServices
Public Module Module1
<Extension()>
Public Sub Add(ByVal list As IList(Of Customer),
ByVal ID As Integer,
ByVal FirstName As String,
ByVal LastName As String)
list.Add(New Customer With {.ID = ID,
.FirstName = FirstName,
.LastName = LastName})
End Sub
End Module
In questo modo creiamo una sorta di overload del metodo Add
dell'interfaccia IList ed il compilatore è in grado di risolvere la
seguente istruzione:
Dim list As New List(Of Customer) From {
{1000, "Mario", "Rossi"},
{1100, "Giuseppe", "Verdi"},
{1200, "Carlo", "Bianchi"}}
come se fosse la precedente e restituendoci una lista di tre
oggetti Customer:

Se analizziamo il codice generato dal compilatore, ci rendiamo
conto che, effettivamente, viene richiamato il nostro metodo di
estensione:

Multiline lambda expression
Nella nuova versione di VB.NET possiamo definire delle lambda
expression su più linee.
Un esempio di ciò che si intende è il seguente:
Dim intList = {1, 4, 7, 3, 8, 2, 5, 9, 0}
Dim query = Array.FindAll(intList,
Function(x)
Dim retVal As Boolean = False
If x > 6 Then
retVal = True
Else
If x \ 2 = 1 Then
Return True
End If
End If
Return retVal
End Function)
Le lambda expression multilinea possono essere sia Function che
Sub.
E' possibile utilizzare le lambda expression multilinea anche
per assegnare dei gestori di eventi "al volo" come nel seguente
esempio:
AddHandler Me.Button2.Click, Sub()
MessageBox.Show("Ciao")
End Sub
Come si comporta, però, il compilatore quando utilizziamo questa
nuova funzionalità del linguaggio.
Compiliamo la seguente classe:
Public Class Class1
Private Sub Main()
Dim intList = {1, 4, 7, 3, 8, 2, 5, 9, 0}
Dim query = Array.FindAll(intList,
Function(x)
Dim retVal As Boolean = False
If x > 6 Then
retVal = True
Else
If x \ 2 = 1 Then
Return True
End If
End If
Return retVal
End Function)
End Sub
End Class
ed analizziamo il risultato della compilazione:

Come possiamo osservare il compilatore genera un metodo statico
privato chiamato _Lambda$__1 in cui inserisce il codice presente
nella lambda multilinea (tra le altre cose ottimizzato togliendo
l'If..Then ed utilizzando ElseIf).
Creare delle lambda multilinea non è molto diverso dal creare un
metodo che esegue lo stesso algoritmo se non per il fatto che il
compilatore utilizza metodi statici che occupano meno memoria. Il
consiglio è di utilizzare le lambda multilinea dove è strattamente
necessario senza abusarne.
Conclusioni
La versione 10 di Visual Basic.NET implementa molte delle
caratteristiche già presenti all'interno della versione 3.5 di C#
in un'ottica di standardizzazione dei linguaggi che non può che
essere vista positivamente (almeno dagli sviluppatori VB).
Download sorgenti
Tags: Visual Studio 2010,VB.NET