ByRef vs ByVal generates errors?
ByRef
vs ByVal
generates errors!?
I had a method that used an Object
Function Foo(ByRef bar as CustomObject) as Boolean
this method generated errors, because some strange .NET Runtime things changed the bar
object, causing its Dispose()al.
A lot of time spent to understand the thing(where the ..开发者_StackOverflow中文版. object is changed), until somebody replaced ByRef
by ByVal
and object wasn't change anymore when passing to this method...
Somebody could explain this, what happens?
Nota Bene (edit)
As in my case the function Foo
does NOT modify the bar
, shouldn't ByRef
or ByVal
have the same effect?
The Foo
just read the Properties from bar
.
Code:
Module Module1
Sub Main()
Dim b As New Bar
' see the output bellow '
Foo(b.Name)
Console.ReadLine()
End Sub
Function Foo(ByRef name As String) As Boolean
Console.WriteLine("Name is : '{0}'", name)
End Function
Class Bar
Private _Name As String = "John"
Property Name()
Get
Return _Name
End Get
Set(ByVal value)
If _Name IsNot Nothing Then
'_Name.Dispose() If this were an IDisposable, would have problems here'
End If
Console.WriteLine("Name is Changed to '{0}'", value)
End Set
End Property
End Class
End Module
Output:
Name is : 'John'
Name is Changed to 'John'
Passing an argument ByRef
means that if someone assigns a new value to the variable, that new value will be passed back to the calling function. Passing it ByVal
passes a copy of that value to the function, so changes are not propagated back to the caller.
Note that when I refer to the value, it's what's actually stored in that variable. With reference types, that means that it's the reference. Passing a reference type by value does not copy the entire instance, it just copies the reference. This means that any changes made to the object itself will still be visible to the calling function.
For example, consider we have this class:
Public Class Foo
Private m_Value as string
Public Property Value as String
Get
return m_Value
End Get
Set(Value as String)
m_Value = Value
End Set
End Property
End Class
And in our program we have two functions:
Public Sub DoWork(ByVal obj as Foo)
obj = Nothing
End Sub
Public Sub DoWorkRef(ByRef obj as Foo)
obj = Nothing
End Sub
And we call them like this:
Dim obj1 as new Foo()
Dim obj2 as new Foo()
obj1.Value = "bar"
obj2.Value = "baz"
DoWork(obj1)
DoWorkRef(obj2)
At the end of this function, obj1
will still have a value, but obj2
will be Nothing
. This is because obj1
is being passed by value, so the code in DoWork
is operating on a copy of that variable (again, it's the same instance, it's just the variable that's different), whereas obj2
is being passed by reference, so it's pointing at the same variable as the main code.
To point out the "same instance", let's say we changed the functions to be like this:
Public Sub DoWork(ByVal obj as Foo)
obj.Value = "beep"
End Sub
Public Sub DoWorkRef(ByRef obj as Foo)
obj.Value = "bop"
End Sub
If we were to run the same code over again, we'd end up with obj1.Value
being equal to "beep", and obj2.Value
being equal to "bop". This is because even though we're passing obj1
by value, the value is a reference. You now just have two variables pointing at the same instance, so anything done do that instance will be reflected in both variables.
The important thing to remember is that the only effective difference between ByRef
and ByVal
comes when you assign a new value to the variable itself. All other behavior is effectively the same.
Post-Question-Edit Edit
You aren't passing a variable as the ByRef
parameter: you're passing a property. While C# will not allow this (because of this very issue), VB.NET will allow it. If you pass a property as a ByRef
parameter, it's essentially like doing this:
Dim _temp as String = b.Name
Foo(_temp)
b.Name = _temp
In other words, when passing a property as a ByRef
parameter, the setter for that property is always called using the value present in the variable after executing the function, even if the value didn't change.
It's a good rule of thumb not to pass properties as ByRef
parameters.
In VB.NET, passing a property by reference updates the actual property, not the underlying value. Thus, when Foo completes, the CLR calls the Property Set method to update the property value with whatever the new value is at the end of the function (even if it hasn't changed).
This behaviour is described in the VB.NET Language Specification (Reference Parameters section, last 3 paragraphs):
http://msdn.microsoft.com/en-us/library/aa711958(v=VS.71).aspx
Well yes, if you change from declaring a parameter as ByRef
to ByVal
, that will change the behaviour - which make break your code. That's one reason why in C# it has to be specified in the calling code as well.
If there was no difference between ByRef
and ByVal
, we wouldn't bother having them both.
I have an article about parameter passing which may help you understand the difference between them; it's written in C#, but the core principles are the same.
Two things stood out to me:
this method generated errors, because some strange .NET Runtime things changed the bar object, causing its Dispose()al.
and
somebody replaced ByRef by ByVal and object wasn't change anymore
Remember that variables for reference types in .Net work a lot like pointers, in that the variable is only a sort of handle to the actual object off in memory somewhere. When you pass this object to a function ByVal (the default in .Net, and you shouldn't change from the default unless you know the implications), you make a copy of the handle or reference. When you pass the object ByRef, you pass the reference itself.
This means that if you were to do something like assign a reference to a different object to your bar variable in the function, you've changed the original variable outside of the function as well. Even though your function never changed the object, by making that assignment you changed the reference to the object outside of the function as well, and in so doing likely lost that refence -- hence the object is disposed.
精彩评论