views:

87

answers:

4

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.

  1. Why is it legal to pass Me ByRef? (Is there some backwards-compatibility explanation?)
  2. Am I correct in saying that the behavior of doing this is overridden by the compiler? If not, what am I missing?
A: 

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

Fredou
I knew what I would've needed to do to make it "work"; I'm more interested in learning why the method as I originally defined it was legal in the first place. Surely you can agree with me it's quite strange.
Dan Tao
it is strange, it should at least crash since it doesn't even work
Fredou
+4  A: 

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.

adam0101
Wow, so it really does just override the (ridiculous) behavior specified in the code! Very interesting.
Dan Tao
+1  A: 

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 !!!

FastAl
Just to clarify: the behavior I said I expected was **not** what I was "hoping"; I completely understand why assigning `Me` to a new instance would be horrendous. I was just baffled that it was legal in the first place; the fact that it *didn't work* only compounded my confusion.
Dan Tao
@Dan Tao - Sorry for being pessimestic! Don't take offense. I take all this text that comes out of the screen to be automatically generated, not coming from a real human being. So think of it as insulting a computer, not a person. The real reason for my horror story was not to insult anybody, but rather, because I had a lot of fun imagining what would happen and then writing about it!
FastAl
+1  A: 

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.

zinglon
Ah, interesting! So, I didn't realize it, but I could also define an `AssignInteger(ByRef x As Integer)` method and actually pass the *value* `5` to it: `AssignInteger(5)` (I just tested this). And the compiler would create a temporary variable in place of the value, effectively changing the method to behave as if `x` were declared `ByVal`. Honestly I find it very strange that this behavior is legal, but at least it's specified!
Dan Tao
...wow, and I just noticed: *not compatible with the reference parameter's type*?!? Man, so I could *also* define an `AssignObject(ByRef x As Object)` method and pass a variable declared as `String` to it! This just keeps getting weirder and weirder (why does VB.NET allow such nonsense, I wonder?)...
Dan Tao
In this particular case, I'd wager that most of the time it will Just Work. In real code, you probably know what the function will be doing when you call it and the compiler is just saving you some effort. It seems to fit with the overall "helpful" feel of the language, from what little experience I have. (I admit to working almost exclusively with languages that try to protect me from myself.)
zinglon