【问题标题】:Why does this extension method throw a NullReferenceException in VB.NET?为什么这个扩展方法在 VB.NET 中会抛出 NullReferenceException?
【发布时间】:2011-01-25 01:12:54
【问题描述】:

根据之前的经验,我的印象是在空实例上调用扩展方法是完全合法的(尽管可能并不可取)。所以在 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 的东西,而不是特定于语言的东西,但也许我错了。)

谁能给我解释一下?

【问题讨论】:

  • IsNull 方法只是一个示例,还是您真的想使用 x.IsNull() 而不是 'x Is Nothing' 或 'x == null'?
  • @jrummell:这只是一个例子。正如我所提到的,我正在编写一些示例代码来说明扩展方法如何为我的一些团队成员工作。我打算让这个方法包含一个注释,“你实际上可以用扩展方法来做到这一点,尽管我不推荐它”——只是为了展示一个扩展方法实际上只是一个静态的(共享) 方法。但后来我发现我在 VB 中无法做到这一点,这让我很惊讶。
  • 正如已经发现的那样,这是由于对后期绑定的支持......像你一样 (Dan) 我没有意识到 VB 对后期绑定有这样的支持,我猜这是一个兼容性问题用VB6。顺便说一句,如果您正在考虑为其他开发人员制定一些标准/培训,您可能希望强制使用 Option Strict,因为我认为它有助于解决很多潜在问题。当然是 YMMV。

标签: .net vb.net extension-methods nullreferenceexception


【解决方案1】:

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

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

参考:

【讨论】:

  • 啊,这确实有些道理......虽然我一直很生气,但 C#/VB 之间的差异/不一致。从这些链接文章中获取的关键词是; “当你指定对象时,它的意思是“采取任何其他而不是一个对象。”太棒了。8-)
  • @roygbiv:作为一个不经常在 VB.NET 中编码的人,我真的不知道链接中讨论的那些后期绑定方法调用是可能的。 (我会猜到 VB.NET 的后期绑定能力要小得多。)所以感谢你教给我一些新的东西,感谢你第一个在这里找到我认为是“真正”的答案。
  • @DanTao: 我希望 VB.NET 允许将一个变量声明为System.Object 并让它简单地表现为一个类引用,其基类型恰好是System.Object,无需特殊处理与Object关联。
【解决方案2】:

更新:

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

出于此Connect issue 中所述的原因,此行为是设计使然:

VB 允许你在 Object 上调用扩展方法 defined,但仅限于 如果变量不是静态的 类型为 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

【讨论】:

  • 我想知道 - 如果扩展方法位于用 C# 编写的库中,vb 会以 C# 方式调用扩展方法吗?
  • 和@Joel:我刚刚对此进行了测试——将扩展方法放在 C# 库中,从 VB 中调用它。 VB 代码仍然抛出了NullReferenceException。 Gareth 似乎在做某事:仅当将扩展方法应用于 System.Object 而不是更具体的事情时,此问题才会存在。
  • 确实,这是您尝试调用扩展方法的对象的类型,而不是函数期望的类型。也就是说,IsNull(ByVal Obj as Object) 可以与 Dim A As String ... A.IsNull 一起工作,即使 A 什么都没有。 roygbiv 似乎有相关链接说明原因。
  • @Joel Coehoorn:我最初的回答是不正确的,VB 确实以相同的方式调用扩展方法,但在这种特定情况下(编译器创建后期绑定调用)它根本不支持扩展方法而是)。
  • 很好,内容丰富的答案。不过,我不得不把它交给 roygbiv,因为我认为他首先解决了根本问题(这是一个特定于 System.Object 类型的设计决策)。
【解决方案3】:

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

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

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

【讨论】:

  • +1。只有当类型不是 Object 时,扩展方法才允许使用 Nothing。因此,它似乎与运行时对象的性质有关。
【解决方案4】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-26
    • 2015-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-27
    • 2014-03-07
    • 1970-01-01
    相关资源
    最近更新 更多