- Visual Basic .Net: come ottenere le stesse prestazioni di C# -
 
COSA SERVE PER QUESTO TUTORIAL
Download | Chiedi sul FORUM | Glossario conoscenza base di VB .Net
Considerazioni su sintassi, late-binding, cast e controllo degli overflow in VB .Net

PREGI E DIFETTI DI VISUAL BASIC
La sintassi di VB.

Spesso si sente parlare male di VB .Net e al suo posto suggeriscono l'uso di C#. Ebbene sotto certi aspetti VB .Net è del tutto equivalente a C#, sotto altri inferiore, sotto altri ancora è persino superiore. Trascurando i semplici fattori di gusto è innegabile che la sintassi di Visual Basic sia chiara, semplice, intuitiva e quasi paragonabile a pseudo-codice (ovvero comprensibile da chiunque abbia una minima cognizione di programmazione). In alcuni contesti risulta anche molto più chiara, in particolare in ASP .Net dove spesso ci si trova a mischiare codice HTML e linguaggio .Net; si pensi ad un codice molto articolato, ricco di condizioni If, cicli e altro ancora: nei linguaggi C-like (quale C# è) non è facilmente intuibile a cosa si riferisca una chiusa graffa ("}"), se non grazie all'indentazione e comunque in presenza nella stessa schermata dell'inizio del blocco. Si tratta del termine di un ciclo? Di un If? In Visual Basic questo problema viene risolto costringendo chi scrive il codice ad esplicitare che cosa si sta chiudendo (End If, Loop, End Sub e così via), inoltre chiudendo un ciclo For è anche possibile specificare la variabile su cui si sta ciclando, il che rende possibile distinguere facilmente dove ci si trova se si è in presenza di più cicli nestati (uno dentro all'altro).


For CUtenti As Integer = 1 To 10
    [...]
    For CFigli As Integer = 1 To 3
        [...]
    Next CFigli
    [...]
Next CUtenti

Certamente la sintassi Basic è estremamente prolissa, ma con i moderni ambienti di sviluppo questo problema non tocca molto chi scrive codice, grazie a funzionalità di auto-completamento e simili.
Tra gli svantaggi della sintassi VB rispetto a quella C-like vi sono poi certamente la minore flessibilità dei cicli For e delle singole istruzioni, che possono essere divise su più righe come meglio si crede (in VB è possibile ma alla fine della riga è necessario specificare un carattere di underscore, "_") e altre caratteristiche ancora.
Ma quello di cui vogliamo occuparci in questo tutorial sono in realtà gli aspetti per i quali i due linguaggi sono equivalenti, o meglio, possono essere resi tali con alcuni semplici accorgimenti non così noti tra gli sviluppatori. Per rendere un'applicazione scritta in Visual Basic .Net (quasi) equivalente dal punto di vista delle prestazioni a C# bisogna tenere conto in sostanza di tre cose: il late-binding, l'uso dei cast e il controllo sull'overflow degli interi.

LATE BINDING
Gli svantaggi pratici e teorici di un uso indiscriminato del late-binding.

Il late-binding consiste in sostanza nella possibilità di effettuare chiamate "alla cieca", ovvero ad esempio chiamare su una variabile dichiarata come Object il metodo Clear(). Questo metodo ovviamente non appartiene alla classe Object, ma, se il late-binding è abilitato questo fatto non darà errore fino all'esecuzione della riga in questione e non lo darà neppure allora se quella variabile contiene in realtà una oggetto ad esempio di tipo DataTable. Chiariamo il concetto con un esempio:


Dim obj As Object 
obj = New System.Data.DataTable()
obj.Clear()

L'esempio sopra è del tutto lecito, in quanto a run-time il CLR andrà a verificare se l'oggetto obj dispone del metodo e lo chiamerà. Tuttavia questa verifica a run-time richiede un notevole (ed inutile) dispendio di risorse, che possiamo quantificare approssimativamente con le seguenti righe di codice:


Sub Main()
    Console.WriteLine("Attendere 20 secondi.")
    Dim lngCountLB As Long = 0, lngCountNotLB As Long = 0
    Dim dtStop As DateTime

    dtStop = Now.AddSeconds(10)
    Dim objDT As Object
    objDT = New System.Data.DataTable()
    Do While Now < dtStop
        objDT.Clear()
        lngCountLB += 1
    Loop

    dtStop = Now.AddSeconds(10)
    Dim dtDT As System.Data.DataTable
    dtDT = New System.Data.DataTable()
    Do While Now < dtStop
        dtDT.Clear()
        lngCountNotLB += 1
    Loop

    Console.WriteLine("Operazioni in late binding: " & lngCountLB)
    Console.WriteLine("Operazioni senza late binding: " & lngCountNotLB)
    Console.WriteLine("In assenza di late binding vengono eseguite {0} volte più operazioni.", _
        Math.Round(lngCountNotLB / lngCountLB, 2))

    Console.ReadLine()
End Sub

Il codice presentato non fa altro che misurare quante volte in 10 secondi viene chiamato il metodo Clear() su un DataTable definito come Object (quindi servendosi del late-binding) e quante con una variabile con tipizzazione forte (ovvero dichiarata direttamente come DataTable). Per rendere l'idea, sulla macchina su cui è stato testato il metodo senza late-binding è risultato dalle 8 alle 10 volte più veloce. Non è di certo un risultato trascurabile.
Spesso questo tipo di errori vengono commessi involontariamente, ed è per questo molto utile disabilitare il late-binding, in modo che il compilatore dia errore ogni volta che si tenta di utilizzare il late-binding.
Il late-binding è disattivabile in ogni singolo file di codice VB specificando in cima quanto segue:

Option Strict On

O anche a livello di progetto andando sulle opzioni di compilazione ed impostando la casella Option Strict anche qui su On. O ancora da linea di comando specificando il parametro "/optionstrict+" al compilatore di Visual Basic (vbc.exe). Per ASP .Net si può impostare per tutta l'applicazione tramite l'elemento compilation (all'interno della sezione system.web), impostando l'attributo strict su true oppure, in una pagina specifica, nella direttiva Page, sempre impostando l'attributo strict su true.
Attivando Option Strict vengono anche disabilitati i cast impliciti, ovvero una linea simile alla seguente genererebbe un errore di compilazione:

Dim strStringa As String = 123

Questo perché implicitamente (con Option Strict disattivato) avverrebbe un cast su 123 dal tipo Integer a String. Sebbene questa restrizione possa inizialmente apparire fastidiosa, col tempo si rivela utile nell'evitare errori difficoltosi da individuare. La linea corretta dovrebbe essere quindi:

Dim strStringa As String = CStr(123)
I CAST
Conoscere quale tipo di cast usare per ottimizzare le prestazioni.

Il secondo punto a cui bisogna prestare attenzione è l'uso dei cast. In generale per effettuare cast verso tipi semplici come String, Integer, Double e così via è buona praticata usare le corrispettive parole chiave: CStr, CInt, CDbl e così via. Ad esempio:


Dim bln As Boolean = CBool("true")
Dim byt As Byte = CByte("12")
Dim chr As Char = CChar("x")
Dim dat As Date = CDate("1/1/1950")
Dim dbl As Double = CDbl("3,14")
Dim dec As Decimal = CDec("79228162514264337593543950335")
Dim int As Integer = CInt("23498")
Dim lng As Long = CLng("23874089")
Dim obj As Object = CObj("Sono un oggetto.")
Dim sbyt As SByte = CSByte("-123")
Dim shr As Short = CShort("44")
Dim sng As Single = CSng("6,28")
Dim str As String = CStr(123)
Dim uint As UInteger = CUInt("442")
Dim ushr As UShort = CUShort("77")

Per le conversioni da un tipo complesso ad un altro si deve invece usare CType o DirectCast a seconda delle evenienze. CType tenta sempre di effettuare una conversione, ad esempio dalla stringa "123" al corrispondente valore Integer, al contrario DirectCast non esegue conversioni ma semplicemente cambia il tipo, se possibile, ovvero quando l'oggetto su cui si sta effettuando il cast eredita o è il tipo di destinazione. Immaginiamo di avere la seguente Sub:


Sub Elabora(Input As Object, TipoDiInput As Integer)
    Select Case TipoDiInput
        Case 1 'DataTable
            Dim dt As DataTable
            dt = DirectCast(Input, Data.DataTable)
            'Elabora la DataTable
        Case 2 'String
            Dim str As String
            str = DirectCast(Input, String)
            'Elabora la stringa
        Case 3 'Integer
            Dim int As Integer
            int = DirectCast(Input, Integer)
            'Elabora il numero
    End Select
End Sub

Nota: questo codice è solo a scopo esemplificativo della funzione di DirectCast, una routine simile è una pessima tecnica di programmazione, per ottenere lo stesso risultato in maniera pulita si dovrebbero scrivere tre overload della funzione che prendono un solo parametro di tipo diverso (DataTable, String e Integer).

La funzione in questione effettua un cast su un parametro generico di tipo Object verso un tipo suggerito dal secondo parametro, ma senza effettuare alcuna conversione sui dati, semplicemente esplicita (espone) il tipo di Input che era "nascosto" all'interno della variabile di tipo Object.
Vediamo un altro esempio per chiarire ulteriormente. La seguente riga non funzionerebbe, poiché richiede una conversione:

Dim int As Integer = DirectCast("123", Integer)

Ma funzionerebbero le seguenti:


Dim dt As New Data.DataTable()
Dim obj As Object
obj = dt

Dim dt2 As Data.DataTable
dt2 = DirectCast(obj, Data.DataTable)

Infatti la variabile obj contiene in realtà una DataTable e riconvertirla al suo tipo originario non richiede alcuna conversione. In questo caso utilizzare CType sarebbe stato meno efficiente, per questo è importante capire quando è necessario utilizzare l'uno o l'altro operatore.

IL CONTROLLO SULL'OVERFLOW DEGLI INTERI
La questione dell'overflow durante operazioni aritmetiche.

L'ultimo punto a cui prestare attenzione per avere prestazioni equivalenti a C# è il controllo dell'overflow degli interi. Consideriamo la seguente linea di codice:

Dim int As Integer = Integer.MaxValue + 1

Visual Basic, di default, esegue un controllo di overflow degli interi, ovvero quella riga darebbe errore poiché si sta tentando di sommare un unità al massimo numero che un Integer può contenere.
Il controllo sull'overflow degli interi comporta però una certa perdita di prestazioni, ed è per questo disattivabile. Tuttavia questo può causare seri problemi di sicurezza e stabilità se al suo posto non vengono effettuati opportuni controlli manuali. Consideriamo il seguente codice:


'Possono avere accesso solo le persone con
'un nome e cognome lungo meno di 30 caratteri
Dim intLunghezzaNome As UInteger
Dim intLunghezzaCognome As UInteger
Console.Write("Quanti caratteri compongono il tuo nome? ")
intLunghezzaNome = CUInt(Console.ReadLine())
Console.Write("Quanti caratteri compongono il tuo cognome? ")
intLunghezzaCognome = CUInt(Console.ReadLine())

If intLunghezzaNome + intLunghezzaCognome < 30 Then
    Console.WriteLine("Accesso accordato.")
Else
    Console.WriteLine("Accesso negato.")
End If

Se l'utente inserisse ad esempio 12 per il nome e 4294967295 (o comunque il valore UInteger.MaxValue) la somma dei due valori darebbe 11, poiché l'overflow della somma porterebbe dal valore massimo al valore minimo (0 per un UInteger) e quindi a crescere fino a 11. In questo caso basterebbe semplicemente imporre restrizioni sull'input dell'utente, ma altri casi possono essere molto più articolati e non verranno approfonditi in questo articolo.
C# offre le parole chiave checked e unchecked per limitare il controllo sull'overflow degli interi solo a determinate porzioni di codice, tuttavia in Visual Basic .Net non esiste un corrispettivo, motivo per cui bisogna ponderare con attenzione la scelta di disabilitare totalmente il controllo sugli overflow di interi. Per quantificare le risorse che vengono impiegate per il controllo si pensi che si tratta di un'istruzione ASM in più da eseguire per ogni somma (per effettuare la verifica, se poi vi è overflow è necessario sollevare un'eccezione che comporta un overhead maggiore), ovvero un JNO (salta ad una certa posizione se non vi è stato overflow). Provando a comparare le prestazioni dello stesso codice (di sole somme, un caso molto particolare dunque) con e senza i controlli, quest'ultimo è risultato più veloce di circa il 7%, tutto sommato non un sacrificio insostenibile considerando i vantaggi dal punto di vista della sicurezza e della stabilità che porta. Bisogna inoltre tener presente che può essere davvero arduo in alcune occasioni individuare un overflow a mano (sottinteso che ignorare il problema non è una soluzione) e in generale potrebbe richiedere addirittura più tempo del JNO che predisporrebbe invece il compilatore.
In ogni caso, per disattivare il controllo da linea di comando basta specificare il parametro "/removeintchecks+", mentre dalle opzioni di un progetto, nella scheda Compilazione andare alla voce Opzioni avanzate di compilazione e quindi attivare la casella Rimuovi controllo sull'overflow degli interi.

 

<< INDIETRO by VeNoM00