为什么在VB.NET中传递“Me”ByRef是合法的?

时间:2010-10-05 12:58:33

标签: vb.net compiler-construction byref this-pointer

刚才我震惊地发现以下是合法的(C#等价物绝对不是):

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

但是什么甚至是陌生人对我来说同样陌生的是它似乎并没有我所期望的:

Dim a As New Assigner With {.Field = 10}

a.AssignNew()

Console.WriteLine(a.GetField())

以上输出“10”,而非“0”,就像我认为的那样(虽然很自然,这种期望本身也注入了某种恐怖)。所以你似乎可以传递Me ByRef,但编译器以某种方式覆盖(?)行为就好像一样已通过Me ByVal

  1. 为什么通过Me ByRef合法?(是否有一些向后兼容性解释?)
  2. 我是否正确地说编译器会覆盖执行此操作的行为?如果没有,我错过了什么?

4 个答案:

答案 0 :(得分:5)

编译器似乎将“Me”转换为一个变量,然后传递给ByRef。如果您编译代码,然后使用Reflector打开它,您可以看到发生了什么:

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

因此,当您调用AssignNew()时,您正在将新实例分配给内部生成的变量。 “a”变量不会被触及,因为它甚至不是函数的一部分。

答案 1 :(得分:5)

此行为实际上直接来自Visual Basic规范。

  

11.4.3实例表达

     

实例表达式是关键字MeMyClassMyBase。实例表达式(仅可在非共享方法,构造函数或属性访问器的主体中使用)被归类为

     

9.2.5.2参考参数

     

如果传递给引用参数的变量类型与引用参数的类型不兼容,或者如果将非变量作为参数传递给引用参数,则为临时变量可以被分配并传递给参考参数。传入的值将在调用方法之前复制到此临时变量中,并在方法返回时将其复制回原始变量(如果有的话)

(所有强调我的)

因此,编译器将创建一个分配给Me值的临时变量作为ByRef参数传递。返回后,由于Me不是变量,因此不会生成结果值的副本。

答案 2 :(得分:1)

这只是程序员可以做出的数千种“几乎错误”中的一种。事实上,MS抓住了他们中的大多数人,有时我会惊讶地发出了多少警告。

他们错过了这个。

至于为什么它不改变'我',这是一件好事!当您使用“我”时,为了安全起见,它只会传递您正在使用的真实班级的副本。如果这是他们希望的方式,我们会谈论 GIANT 副作用。你在课堂上的方法无辜地工作,而他们 BAM 突然间你处于一个完全不同的对象中!那太可怕了!如果您打算这样做,您可能只需编写一段spagetti MS-Basic行编号代码,其中所有全局变量都是随机设置的,并且没有子函数。

如果在括号中传递参数,它的工作方式是相同的。例如,这可以按预期工作:

Assign(Reference_I_Want_To_Set, New Assigner)

但这并没有改变任何事情:

Assign((Reference_I_Want_To_Set), New Assigner)

如果您反映上述类型的代码,adam101建议您会看到类似的结果。虽然这对括号来说是一个巨大的挫折,但Me !!!

是一件非常好的事情

答案 3 :(得分:0)

要使此代码正常工作,您需要做的是:

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

然后像

一样使用它
    Dim a As New Assigner With {.NewProperty = 10}

    Assigner.AssignNew(a)

我的理解是你在使用它时无法改变对象的引用,所以你需要在共享子中更改它


Me cannot be the target of an assignment以来,代码似乎创建了它的副本,从那时起,您不使用真实对象,而是使用它的副本