23

根据以前的经验,我的印象是在空实例上调用扩展方法是完全合法的(尽管可能不是可取的)。所以在 C# 中,这段代码编译并运行:

// code in static class
static bool IsNull(this object obj) {
    return obj == null;
}

// code elsewhere
object x = null;
bool exists = !x.IsNull();

然而,我只是为我的开发团队的其他成员整理了一小套示例代码(我们刚刚升级到 .NET 3.5,我被分配了让团队加快开发一些新功能的任务可供我们使用),我编写了我认为与上述代码等效的 VB.NET,结果发现它实际上抛出了一个NullReferenceException. 我写的代码是这样的:

' code in module '
<Extension()> _
Function IsNull(ByVal obj As Object) As Boolean
    Return obj Is Nothing
End Function

' code elsewhere '
Dim exampleObject As Object = Nothing
Dim exists As Boolean = Not exampleObject.IsNull()

调试器就停在那里,就好像我调用了一个实例方法一样。我是否做错了什么(例如,我在 C# 和 VB.NET 之间定义扩展方法的方式是否存在一些细微差别)?在 VB.NET 中调用空实例的扩展方法实际上是不合法的,尽管它在 C# 中是合法的吗?(我会认为这是一个 .NET 的东西,而不是一个特定于语言的东西,但也许我错了。)

有人可以向我解释一下吗?

4

4 回答 4

14

您不能在 VB.NET 中扩展对象类型。

主要是,我们不允许从静态类型为“Object”的任何表达式中调用扩展方法。这对于防止您可能编写的任何现有后期绑定代码被扩展方法破坏是必要的。

参考:

于 2010-03-08T16:41:15.983 回答
8

更新:

下面的答案似乎特定于System.Object扩展的情况。当扩展其他类时NullReferenceException,VB 中没有。

由于此Connect 问题中所述的原因,此行为是设计使然:

VB 允许您调用定义在 Object 上的扩展方法,但前提是该变量不是静态类型为 Object。

原因是 VB 还支持后期绑定,如果我们在调用声明为 Object 的变量时绑定到扩展方法,那么无论您是在尝试调用扩展方法还是其他后期方法,都是模棱两可的——具有相同名称的绑定方法。

理论上,我们可以通过 Strict On 来实现这一点,但 Option Strict 的原则之一是它不应该改变代码的语义。如果允许这样做,那么更改您的 Option Strict 设置可能会导致静默重新绑定到不同的方法,从而导致完全不同的运行时行为。

例子:

Imports System.Runtime.CompilerServices

Module Extensions
    <Extension()> _
    Public Function IsNull(ByVal obj As Object) As Boolean
        Return obj Is Nothing
    End Function

    <Extension()> _
    Public Function IsNull(ByVal obj As A) As Boolean
        Return obj Is Nothing
    End Function

    <Extension()> _
    Public Function IsNull(ByVal obj As String) As Boolean
        Return obj Is Nothing
    End Function

End Module

Class A
End Class

Module Module1

    Sub Main()
        ' works
        Dim someString As String = Nothing
        Dim isStringNull As Boolean = someString.IsNull()

        ' works
        Dim someA As A = Nothing
        Dim isANull As Boolean = someA.IsNull()

        Dim someObject As Object = Nothing
        ' throws NullReferenceException
        'Dim someObjectIsNull As Boolean = someObject.IsNull()

        Dim anotherObject As Object = New Object
        ' throws MissingMemberException
        Dim anotherObjectIsNull As Boolean = anotherObject.IsNull()
    End Sub

End Module

事实上,VB 编译器会创建一个后期绑定调用,以防您的变量静态类型为Object

.locals init ([0] object exampleObject, [1] bool exists)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  ldnull
  IL_0004:  ldstr      "IsNull"
  IL_0009:  ldc.i4.0
  IL_000a:  newarr     [mscorlib]System.Object
  IL_000f:  ldnull
  IL_0010:  ldnull
  IL_0011:  ldnull
  IL_0012:  call       
     object [Microsoft.VisualBasic]Microsoft.VisualBasic.
       CompilerServices.NewLateBinding::LateGet(
        object,
        class [mscorlib]System.Type,
        string,
        object[],
        string[],
        class [mscorlib]System.Type[],
        bool[])
  IL_0017:  call       object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Operators::NotObject(object)
  IL_001c:  call       bool [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToBoolean(object)
  IL_0021:  stloc.1
于 2010-03-08T16:30:43.683 回答
3

Object 似乎有些古怪,可能是 VB 中的错误或编译器中的限制,可能需要他的圣洁 Jon Skeet 发表评论!

基本上,它似乎试图在运行时后期绑定 IsNull 调用,而不是调用导致 NullReferenceException 的扩展方法。如果您打开 Option Strict,您将在设计时看到带有红色曲线的。

将 exampleObject 更改为 Object 本身以外的其他内容将允许您的示例代码工作,即使所述类型的值为 Nothing。

于 2010-03-08T16:32:39.413 回答
0

似乎问题在于该对象为空。此外,如果您尝试以下类似操作,您将收到一个异常,指出 String 没有名为 IsNull 的扩展方法

Dim exampleObject As Object = "Test"
Dim text As String = exampleObject.IsNull()

我认为无论您在 exampleObject 中添加什么值,框架都知道它是什么类型。我个人会避免使用 Object 类的扩展方法,不仅在 VB 中,而且在 CSharp 中

于 2010-03-08T16:30:34.800 回答