Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - I Metodi Parte II

Guida al Visual Basic .NET

Capitolo 14° - I Metodi Parte II

<< Precedente Prossimo >>

ByVal e ByRef

Nel capitolo precedente, tutti i parametri sono stati dichiaranti anteponendo al loro nome la keyword ByVal. Essa ha il compito di comunicare al programma che al parametro formale deve essere passata una copia del parametro attuale. Questo significa che qualsiasi codice sia scritto entro il corpo del metodo, ogni manipolazione e ogni operazione eseguita su quel parametro agisce, di fatto, su un'altra variabile, temporanea, e non sul parametro attuale fornito. Ecco un esempio:
Module Module1
    Sub Change(ByVal N As Int32)
        N = 30
    End Sub

    Sub Main()
        Dim A As Int32 = 56
        Change(A)
        Console.WriteLine(A)
        Console.ReadKey()
    End Sub
End Module 
A schermo apparirà la scritta "56": A è una variabile di Main, che viene passata come parametro attuale alla procedura Change. In quest'ultima, N costituisce il parametro formale - il segnaposto - a cui, durante il passaggio dei parametri, viene attribuita un copia del valore di A. In definitiva, per N viene creata un'altra area di memoria, totalmente distinta, e per questo motivo ogni operazione eseguita su quest'ultima non cambia il valore di A. Di fatto, ByVal indica di trattare il parametro come un tipo value (passaggio per valore).
Al contrario, ByRef indica di trattare il parametro come un tipo reference (passaggio per indirizzo). Questo significa che, durante il passaggio dei parametri, al parametro formale non viene attribuito come valore una coppia di quello attuale, ma bensì viene forzato a puntare alla sua stessa cella di memoria. In questa situazione, quindi, anche i tipi value come numeri, date e valori logici, si comportano come se fossero oggetti. Ecco lo stesso esempio di prima, con una piccola modifica:
Module Module1
    Sub Change(ByRef N As Int32)
        N = 30
    End Sub

    Sub Main()
        Dim A As Int32 = 56
        Change(A)
        Console.WriteLine(A)
        Console.ReadKey()
    End Sub
End Module 
Nel codice, la sola differenza consiste nella keyword ByRef, la quale, tuttavia, cambia radicalmente il risultato. Infatti, a schermo apparirà "30" e non "56". Dato che è stata applicata la clausola ByRef, N punta alla stessa area di memoria di A, quindi ogni alterazione perpetrata nel corpo del metodo sul parametro formale si ripercuote su quello attuale.
A questo punto è molto importante sottolineare che i tipi reference si comportano SEMPRE allo stesso modo, anche se vengono inseriti nell'elenco dei parametri accompagnati da ByVal. Eccone una dimostrazione:
Module Module1
    Dim A As New Object

    Sub Test(ByVal N As Object)
        Console.WriteLine(N Is A)
    End Sub

    Sub Main()
        Test(A)
        Console.ReadKey()
    End Sub
End Module 
Se ByVal modificasse il comportamento degli oggetti, allora N conterrebbe una copia di A, ossia un altro oggetto semplicemente uguale, ma non identico. Invece, a schermo appare la scritta "True", che significa "Vero", perciò N e A sono lo stesso oggetto, anche se N era preceduto da ByVal.


La funzioni

Le funzioni sono simili alle procedure, ma possiedono qualche caratteristica in più. La loro sintassi è la seguente:
Function [name]([elenco parametri]) As [tipo]
    '...
    Return [risultato]
End Function 
La prima differenza che salta all'occhio è l'As che segue l'elenco dei parametri, come a suggerire che la funzione sia di un certo tipo. Ad essere precisi, quell'As non indica il tipo della funzione, ma piuttosto quello del suo risultato. Infatti, le funzioni restituiscono qualcosa alla fine del loro ciclo di elaborazione. Per questo motivo, prima del termine del suo corpo, deve essere posta almeno un'istruzione Return, seguita da un qualsiasi dato, la quale fornisce al chiamante il vero risultato di tutte le operazioni eseguite. Non è un errore scrivere funzioni prive dell'istruzione Return, ma non avrebbe comunque senso: si dovrebbe usare una procedura in quel caso. Ecco un esempio di funzione:
Module Module1
    'Questa funzione calcola la media di un insieme
    'di numeri decimali passati come array
    Function Average(ByVal Values() As Single) As Single
        'Total conterrà la somma totale di tutti
        'gli elementi di Values
        Dim Total As Single = 0
        'Usa un For Each per ottenere direttamente i valori
        'presenti nell'array piuttosto che enumerarli
        'attraverso un indice mediante un For normale
        For Each Value As Single In Values
            Total += Value
        Next
        'Restituisce la media aritmetica, ossia il rapporto 
        'tra la somma totale e il numero di elementi
        Return (Total / Values.Length)
    End Function

    Sub Main(ByVal Args() As String)
        Dim Values() As Single = {1.1, 5.2, 9, 4, 8.34}
        'Notare che in questo caso ho usato lo stesso nome
        'per il parametro formale e attuale
        Console.WriteLine("Media: " & Average(Values))
        Console.ReadKey()
    End Sub
End Module 
E un altro esempio in cui ci sono più Return:
Module Module1
    Function Potenza(ByVal Base As Single, ByVal Esponente As Byte) As Double
        Dim X As Double = 1

        If Esponente = 0 Then
            Return 1
        Else
            If Esponente = 1 Then
                Return Base
            Else
                For i As Byte = 1 To Esponente
                    X *= Base
                Next
                Return X
            End If
        End If
    End Function
    
    Sub Main()
        Dim F As Double
        Dim b As Single
        Dim e As Byte

        Console.WriteLine("Inserire base ed esponente:")
        b = Console.ReadLine
        e = Console.ReadLine
        F = Potenza(b, e)
        Console.WriteLine(b & " elevato a " & e & " vale " & F)
        Console.ReadKey()
    End Sub
End Module  
In quest'ultimo esempio, il corpo della funzione contiene ben tre Return, ma ognuno appartiene a un path di codice differente. Path significa "percorso" e la locuzione appena usata indica il flusso di elaborazione seguito dal programma per determinati valori di Base ed Esponente. Disegnando un diagramma di flusso della funzione, sarà facile capire come ci siano tre percorsi differenti, ossia quando l'esponente vale 0, quando vale 1 e quando è maggiore di 1. È sintatticamente lecito usare due Return nello stesso path, o addirittura uno dopo l'altro, ma non ha nessun senso logico: Return, infatti, non solo restituisce un risultato al chiamante, ma termina anche l'esecuzione della funzione. A questo proposito, bisogna dire che esiste anche lo statement (=istruzione) Exit Function, che forza il programma ad uscire immediatamente dal corpo della funzione: inutile dire che è abbastanza pericoloso da usare, poiché si corre il rischio di non restituire alcun risultato al chiamante, il che può tradursi in un errore in fase di esecuzione.
Come ultima postilla vorrei aggiungere che, come per le varibili, non è strettamente necessario specificare il tipo del valore restituito dalla funzione, anche se è fortemente consigliato: in questo caso, il programma supporrà che si tratti del tipo Object.


Usi particolari delle funzioni

Ci sono certe circostanze in cui le funzioni possono differire leggermente dal loro uso e dalla loro forma consueti. Di seguito sono elencati alcuni casi:
  • Quando una funzione si trova a destra dell'uguale, in qualsiasi punto di un'espressione durante un assegnamento, ed essa non presenta un elenco di parametri, la si può invocare senza usare la coppia di parentesi. L'esempio classico è la funzione Console.Readline. L'uso più corretto sarebbe:
    a = Console.ReadLine()
    ma è possibile scrivere, come abbiamo fatto fin'ora:
    a = Console.ReadLine
  • Non è obbligatorio usare il valore restituito da una funzione: nei casi in cui esso viene tralasciato, la si tratta come se fosse una procedura. Ne è un esempio la funzione Console.ReadKey(). A noi serve per fermare il programma in attesa della pressione di un pulsante, ma essa non si limita a questo: restituisce anche informazione dettagliate sulle condizioni di pressione e sul codice del carattere inviato dalla tastiera. Tuttavia, a noi non interessava usare queste informazioni; così, invece di scrivere un codice come questo:
    Dim b = Console.ReadKey()
    ci siamo limitati a:
    Console.ReadKey()
    Questa versatilità può, in certi casi, creare problemi poiché si usa una funzione convinti che sia una procedura, mentre il valore restituito è importante per evitare l'insorgere di errori. Ne è un esempio la funzione IO.File.Create, che vedremo molto più in là, nella sezione C della guida.


Variabili Static

Le variabili Static sono una particolare eccezione alle variabili locali/temporanee. Avevo chiaramente scritto pochi paragrafi fa che queste ultime esistono solo nel corpo del metodo, vengono create al momento dell'invocazione e distrutte al termine dell'esecuzione. Le Static, invece, possiedono soltanto le prime due caratteristiche: non vengono distrutte alla fine del corpo, ma il loro valore si conserva in memoria e rimane tale anche quando il flusso entra una seconda volta nel metodo. Ecco un esempio:
Module Module1
    Sub Test()
        Static B As Int32 = 0
        B += 1
        Console.WriteLine(B)
    End Sub

    Sub Main(ByVal Args() As String)
        For I As Int16 = 1 To 6
            Test()
        Next
        Console.ReadKey()
    End Sub
End Module 
Il programma stamperà a schermo, in successione, 1, 2, 3, 4, 5 e 6. Come volevasi dimostrare, nonostante B sia temporanea, mantiene il suo valore tra una chiamata e la successiva.


<< Precedente Prossimo >>
A proposito dell'autore

C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...