Piccola lezione di byval e di byref per raffaeu
Prova ad eseguire questo codice:
codice:
Public Class Test
Public CampoEsempio As Integer
Public ReadOnly Name As String 'Campo immutabile
Public Sub New(ByVal nName As String)
Name = nName
Console.WriteLine("Costruzione di " & Me.ToString())
End Sub
Public Overrides Function ToString() As String
Return "{Name=" & Name & ";CampoEsempio=" & CampoEsempio.ToString() & "}"
End Function
Protected Overrides Sub Finalize()
Console.WriteLine("Distruzione di " & Me.ToString())
End Sub
End Class
Public Module Principale
Public tst As New Test("Test creato in Main")
Public Sub Main()
tst.CampoEsempio = 0
EsempioByVal1(tst)
Console.WriteLine(tst)
EsempioByRef1(tst)
Console.WriteLine(tst)
EsempioByVal2(tst)
Console.WriteLine(tst)
EsempioByRef2(tst)
Console.WriteLine(tst)
EsempioByVal3(tst)
EsempioByRef3(tst)
End Sub
Public Sub EsempioByVal1(ByVal parametro As Test)
Console.WriteLine("In EsempioByVal1")
parametro.CampoEsempio += 5
End Sub
Public Sub EsempioByRef1(ByRef parametro As Test)
Console.WriteLine("In EsempioByRef1")
parametro.CampoEsempio += 5
End Sub
Public Sub EsempioByVal2(ByVal parametro As Test)
Console.WriteLine("In EsempioByVal2")
Dim zzz As New Test("Test creato in EsempioByVal2")
parametro = zzz
End Sub
Public Sub EsempioByRef2(ByRef parametro As Test)
Console.WriteLine("In EsempioByRef2")
Dim zzz As New Test("Test creato in EsempioByRef2")
parametro = zzz
End Sub
Public Sub EsempioByVal3(ByVal parametro As Test)
Console.WriteLine(parametro Is tst)
End Sub
Public Sub EsempioByRef3(ByRef parametro As Test)
Console.WriteLine(parametro Is tst)
End Sub
End Module
Eseguendo questo programma verrà visualizzato:
codice:
Costruzione di {Name=Test creato in Main;CampoEsempio=0}
In EsempioByVal1
{Name=Test creato in Main;CampoEsempio=5}
In EsempioByRef1
{Name=Test creato in Main;CampoEsempio=10}
In EsempioByVal2
Costruzione di {Name=Test creato in EsempioByVal2;CampoEsempio=0}
{Name=Test creato in Main;CampoEsempio=10}
In EsempioByRef2
Costruzione di {Name=Test creato in EsempioByRef2;CampoEsempio=0}
{Name=Test creato in EsempioByRef2;CampoEsempio=0}
True
True
Distruzione di {Name=Test creato in EsempioByRef2;CampoEsempio=0}
Distruzione di {Name=Test creato in Main;CampoEsempio=10}
Distruzione di {Name=Test creato in EsempioByVal2;CampoEsempio=0}
Esaminiamo le prime 5 righe: si può notare che indipendentemente da come venga passato, le modifiche effettuate ai campi dell'oggetto (anche indirettamente attraverso metodi dell'oggetto stesso) si riflettono anche sulla variabile pubblica tst, poiché sia la variabile tst sia i parametri passati alle funzioni sono SEMPRE E COMUNQUE DEI PUNTATORI che puntano al medesimo oggetto in memoria; il byval e il byref si riferiscono al puntatore, non all'oggetto: se si passa un oggetto byref verrà passato un puntatore al puntatore passato alla funzione, per cui la funzione EsempioByRef1 dovrà andare a guardare in "parametro", in cui troverà un puntatore a "tst", che a sua volta è un puntatore all'oggetto vero e proprio che si trova in memoria. Passando invece l'oggetto per valore, "parametro" in EsempioByVal1 conterrà un puntatore nuovo che punta direttamente all'oggetto. In entrambi i casi ovviamente la funzione può accedere e modificare i campi del medesimo oggetto.
Le sei righe successive e le procedure EsempioByVal2 e EsempioByRef2 servono ad evidenziare le conseguenze della differenza tra ByVal e ByRef riferita a ReferenceTypes: osservando il corpo delle due funzioni si può notare che entrambe creano un nuovo oggetto (identificato da un nome diverso) e tentano di assegnarlo al parametro passato. Come si può vedere dall'output in entrambe le funzioni si verifica la costruzione, ma, mentre con il passaggio ByVal la modifica non si riflette sul puntatore tst, con il passaggio ByRef al puntatore tst viene assegnato l'oggetto creato in EsempioByRef2, poiché mentre a EsempioByVal2 viene passato un nuovo puntatore all'oggetto che anche se viene modificato non influenza nulla, a EsempioByRef2 viene passato un puntatore al puntatore tst, e quindi la funzione può modificare anche la destinazione del puntatore tst, come fa qui.
Le due righe successive dimostrano che sia con il passaggio byval sia con il passaggio byref si hanno dei puntatori (o delle catene di puntatori) ad un medesimo oggetto (in questo caso tst), di cui non viene creata alcuna copia: l'operatore Is, contenuto nelle sub EsempioByVal3 ed EsempioByRef3, restituisce true se i due puntatori puntano al medesimo oggetto, e restituisce true sia con il passaggio byval sia con quello byref.
Per tirare le somme, ByVal parametro As Test è come dire in C "Test ** parametro", mentre ByRef parametro As Test equivale a "Test * parametro".
IMPORTANTE: esiste sì un caso in cui il runtime .NET crea copie di interi oggetti, e non di puntatori: è quello che fa quando si passa ByVal un oggetto che eredita da ValueType, come una struttura (Structure) o un tipo di dato "primitivo", come Integer, Long, ... (System.Int32, System.Int64, ...).