Le novità di VB.NET 10 al microscopio

Scritto da  Massimo Bonanni il giovedì 3 giugno 2010
Linguaggio: VB   •  Framework: 4.0   •  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:

Fig.01

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:

Fig.02

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

Fig.03

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:

Fig.04

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:

Fig.05

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).

Fig.06

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):

Fig.07

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:

Fig.08

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:

Fig.09

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

Fig.10


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:

Fig.11

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

 
x