Why is this different?!
Public Class Form1
Public Function MyFunction() As Integer?
Return Nothing
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim o As Object = Me
MsgBox(TypeName(Me)) ' Form1
MsgBox(TypeName(o)) ' Form1
MsgBox(TypeName(Me.MyFunction())) ' Nothing
MsgBox(TypeName(o.MyFunction())) ' Nothing
' but
MsgBox(TypeName(Me.MyFunction() + 0)) ' Nothing
MsgBox(TypeName(o.MyFunction() + 0)) ' Integer
End Sub
End Class
Using Option Strict On is a pretty good way to avoid surprises like this. You'll get a "what the heck are you trying to do?" error message from the compiler.
But with it Off, these are valid statements, executed by the DLR, the Dynamic Language Runtime. Which is capable of evaluating late-bound expressions like this. It however has a problem with a nullable type like Integer?. It needs to deal with the boxed version of the value. Which is just plain Nothing. And Nothing doesn't have any type information associated with it. There's nothing the DLR can do to see that this started life as a nullable integer, for all it knows it could be a string that is Nothing.
The compiler cannot help either, it cannot emit any code to make the expression follow normal evaluation rules. All it knows is that there is some function, it doesn't know which, whose name is "MyFunction" with no idea what kind of value it returns. It passes the buck to the DLR to sort it out.
So the DLR just punts at it. And it comes up with "No idea" + 0 = 0. Given that it does have type information for 0. It is an Integer so it tries to interpret the left operator as an integer as well. Which is valid, Nothing is a correct default value for Integer.
Feature, not a bug.
Visual Basic .NET had Nothing long before it had nullable value types - it inherited it from pre-.NET Visual Basic. And in some cases, it behaves more like C#'s default(T) then t does null.
Your final call is invoking the AddObject method in the Visual Basic compiler services. This method has existed for a long time, and again pre-dates nullable value types, and unfortunately isn't well documented.
Unfortunately, they couldn't make nullable types behave absolutely consistently, especially in the face of late-bound calls, whilst still maintaining backwards compatibility. For instance, this also prints 0:
Console.WriteLine(CType(CType(Nothing, Object), Int32))
Related
I want to make that variable can be assigned with null. But giving'?' seems doesn't work. Please help me find the way out. I'm new to this. Thanks
Public Class T3DObject
Public V(7) As Point
Public E(5) As Surface
End Class
Public Structure ListElmt3DObject
Public first As Elmt3DObject
End Structure
Public Class Elmt3DObject
Public child As ListElmt3DObject?
Public nxt As Elmt3DObject? 'this line is error
Public obj As T3DObject? 'this also error
End Class
Is there any other way? Please share. Thank you!!
This is the code that make error: NullReferenceException
Sub Process(ByRef E As Elmt3DObject)
While E.child IsNot Nothing Or E.nxt IsNot Nothing
'code
End While
End Sub
I know this is a VB.Net question, but to understand this it's helpful to understand some of how C# works, too. In C#, there is a concept of null. You can assign a null value to reference types (ie: VB.Net Class), but you cannot assign null to value types (ie: VB.Net Structure). Value type (Structure) variables always have a value of some kind.
Coming back to VB.Net, we have the Nothing keyword. However, Nothing is not a direct analog to C#'s null. It's closer to match to the C# default(T) expression. VB.Net allows you to assign Nothing to value types. It's just that when you do, you get the default value for the type.
The Nullable(Of T) type and associated language syntax features were designed to allow newer null semantics for value types. This doesn't apply to reference types, because reference types already have a concept of null.
While I'm here, I also see this:
Sub Process(ByRef E As Elmt3DObject)
Elmt3DObject is a reference type (Class). It's almost always a mistake to pass a reference type ByRef. You still want ByVal, because passing ByVal for Classes still passes the reference. It makes a copy of the reference, but it's still just a reference.
I am not much familiar with Visual Basic 6.0 and am not having a VB compiler installed, but I was looking at some VB code for some debugging and saw this:
Private Function IsFieldDeleted(oLayoutField As Object)
Dim oColl As Collection
Set oColl = GetFieldIdsForField(oLayoutField)
IsFieldDeleted = (oColl.Count = 0)
Set oColl = Nothing
End Function
In other functions I see they define the return type with an "As" for example "As Boolean" but this one does not have an "As" :D and then how they have used it is like this:
If Not IsFieldDeleted(oRptField.GetUCMRLayoutField) Then
Call oCollection.Add(oRptField, oRptField.ObjectKeyString)
Call AddToNewLineSeperatedString(sCaseFldDescMsg, oFld.FieldDescription)
End If
How is this working? Is it just like rewriting it and saying that the function returns an integer and compare the return type to be either 0 or 1? Or are there some other hidden tips in there?
When no type is specified, in VB.NET it assumes Object for the return type. In VB6, it assumes Variant. In VB.NET you can make things much more obvious by turning Option Strict On, but I don't believe that option was available in VB6.
The value that is returned, in reality, is still typed as a Boolean, but you are viewing the returned value as a Variant. So, to do it "properly", you really ought to cast the return value like this:
If Not CBool(IsFieldDeleted(oRptField.GetUCMRLayoutField)) Then
....
End If
Calling CBool casts the value to a Boolean instead of a Variant. This is unnecessary, though, since VB will use late-binding to determine the type of the return value is a boolean.
The best thing to do in this case is to change the function to As Boolean. Doing so will not break any existing code since that's all it ever returned anyway. However, if it's a public member in a DLL, that would break compatibility.
It's really bugging me that the VS 2010 IDE isn't barking at me for trying to pass Nothing through a method parameter that takes an user-defined enum. Instead, it's passing 0 through to the method. c# would never allow this. Is there some module-level modifier I can add like option strict that will force the IDE to not allow these types of implicit conversions?
Sadly, no.
But you can assign values to your enumeration members while skipping 0 (or use a placeholder named None or something like that), and at least handle this case at run time.
Sub Main
MyMethod(Nothing) ' throws Exception
End Sub
Sub MyMethod(e as MyEnum)
If e = 0 Then
Throw New Exception
End If
End Sub
Enum MyEnum
a=1
b=2
c=3
End Enum
Nothing is the equivalent of default in the C# language. So no.
Reconsider your programming style, Nothing should be used very sparingly. Basically only in generic code, same place you'd use default in C#. You don't need it anywhere else, VB.NET doesn't insist on variable initialization like C# does. Any variable of a reference type gets initialized to Nothing automatically. Cringe-worthy to a C# programmer perhaps, but entirely idiomatic in VB.NET code.
I just installed Visual Studio 2010 Service pack (proposed on Windows Update), and I can see a new feature on the "intellisense" that means when I write a Function or Sub in VB.NET it doesn't auto-complete parameters with ByRef or ByVal...
1) Is there anyway that I can configure this option back to how it was before?
2) If I don't specify ByX, which one is used by default? (it seems like it is always ByRef)
It seems that this post covers your question:
http://msmvps.com/blogs/carlosq/archive/2011/03/15/vs-2010-sp1-changing-quot-byval-quot-vb-net-code-editor-experience.aspx
So no, there is no way to get the old behaviour. From now on ByVal is the default (what it was before) and it won't get added automatically to the method parameters.
In my opinion this is a good decision since it's making VB.NET a bit more consistent with C# and avoids unnecessary "noises"(it's already verbose enough).
Old behaviour:
Private Sub test(ByVal test As String)
End Sub
New behaviour
Private Sub test(test As String)
End Sub
Tim covered what you asked directly, but something else to keep in mind is that any reference type variable, like a user defined class even if passed by value will allow you to make changes to that instances properties etc that stay. It won't however allow you to change the entire object. Which may be why it seemed to you to be defaulting to by reference
Public Sub (Something As WhateverClass)
Something = New WhateverClass 'will result in no changes when outside this method
Something.Property1 = "Test" 'will result in an updated property when outside this method
End Sub
From MSDN:
The value of a reference type is a pointer to the data elsewhere in memory.
This means that when you pass a reference type by value,
the procedure code has a pointer to the underlying element's data,
even though it cannot access the underlying element itself. For
example, if the element is an array variable, the procedure code does
not have access to the variable itself, but it can access the array
members.
Beware when transferring routines to VBA, where the default is ByRef (see, e.g., "The Default Method Of Passing Parameters" at the bottom of this page, by the great Chip Pearson).
That can be messy.
I was shocked just a moment ago to discover that the following is legal (the C# equivalent is definitely not):
Class Assigner
''// Ignore this for now.
Public Field As Integer
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Sub AssignNew()
''// Passing "Me" ByRef???
Assign(Me, New Assigner)
End Sub
''// This is just for testing.
Function GetField() As Integer
Return Me.Field
End Function
End Class
But what's even stranger just as strange to me is that it doesn't seem to do what I expect:
Dim a As New Assigner With {.Field = 10}
a.AssignNew()
Console.WriteLine(a.GetField())
The above outputs "10," not "0" like I thought it would (though naturally, this expectation was itself infused with a certain kind of horror). So it seems that you can pass Me ByRef, but the behavior is somehow overridden (?) by the compiler to be as if you had passed Me ByVal.
Why is it legal to pass Me ByRef? (Is there some backwards-compatibility explanation?)
Am I correct in saying that the behavior of doing this is overridden by the compiler? If not, what am I missing?
This behavior actually follows pretty directly from the Visual Basic specification.
11.4.3 Instance Expressions
An instance expression is the keyword Me, MyClass, or MyBase. An instance expression, which may only be used within the body of a non-shared method, constructor, or property accessor, is classified as a value.
9.2.5.2 Reference Parameters
If the type of the variable being passed to a reference parameter is not compatible with the reference parameter's type, or if a non-variable is passed as an argument to a reference parameter, a temporary variable may be allocated and passed to the reference parameter. The value being passed in will be copied into this temporary variable before the method is invoked and will be copied back to the original variable (if there is one) when the method returns.
(All emphasis mine)
So, the compiler will create a temporary variable assigned to the value of Me to be passed as the ByRef parameter. Upon return, no copy of the resulting value will take place since Me is not a variable.
It appears the compiler transforms "Me" into a variable which is then passed ByRef. If you compile your code, then open it with Reflector, you can see what's happening:
Class Assigner
''// Methods
Public Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
Public Sub AssignNew()
Dim VB$t_ref$S0 As Assigner = Me
Me.Assign((VB$t_ref$S0), New Assigner)
End Sub
Public Function GetField() As Integer
Return Me.Field
End Function
''// Fields
Public Field As Integer
End Class
So it looks like when you call AssignNew(), you are assigning the new instance to the internally generated variable. The "a" variable doesn't get touched because it's not even a part of the function.
This is just one of the thousands of possible 'almost errors' a programmer can make. MS caught most of them, in fact, sometimes I'm suprised at how many warnings do come up.
they missed this one.
As far as why it doesn't change 'me', it's a darn good thing! When you use 'me', it just passes a copy of the real class you are working with, for safety purposes. If this worked they way you were hoping, we would be talking GIANT side-effect. You're innocently working away with in your class' methods, and them BAM all of a sudden you are in an ENTIRELY different object! That would be awful! If you're going to do that, you might as well just write a piece of spagetti MS-Basic line-numbered code with all globals that get randomly set, and no subs/functions.
The way this works is the same way if you pass arguments in parenthesis. For example this works as expected:
Assign(Reference_I_Want_To_Set, New Assigner)
But this doesn't change anything:
Assign((Reference_I_Want_To_Set), New Assigner)
If you reflect the above type of code as adam101 suggests you will see similar results. While that is huge frustration with the parenthesis, it is a very good thing with Me !!!
what you need to do to make this code work is this:
Class Assigner
''// Ignore this for now.
Private newPropertyValue As Integer
Public Property NewProperty() As Integer
Get
Return newPropertyValue
End Get
Set(ByVal value As Integer)
newPropertyValue = value
End Set
End Property
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Shared Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Shared Sub AssignNew(ByRef x As Assigner)
''// Passing "Me" ByRef???
Assign(x, New Assigner)
End Sub
End Class
then use it like
Dim a As New Assigner With {.NewProperty = 10}
Assigner.AssignNew(a)
my understanding is you cannot change the reference of the object while using it, so you need to change it in a shared sub
since Me cannot be the target of an assignment, the code seem to create a copy of it and from that point on, your not using the real object, but a copy of it