为什么此扩展方法在 VB.NET 中引发 NullReferenceException?

Why does this extension method throw a NullReferenceException in VB.NET?

提问人:Dan Tao 提问时间:3/9/2010 最后编辑:Dan Tao 更新时间:3/9/2010 访问量:3011

问:

根据以前的经验,我的印象是在 null 实例上调用扩展方法是完全合法的(尽管可能不可取)。因此,在 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 之间定义扩展方法的方式是否存在一些细微差别)?尽管在 C# 中是合法的,但在 VB.NET 中对 null 实例调用扩展方法实际上并不合法吗?(我本来以为这是 .NET 的东西,而不是特定于语言的东西,但也许我错了。

谁能向我解释一下这个?

.NET vb.net 扩展方法 nullreferenceexception

评论

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

答:

0赞 Damiano Fusco 3/9/2010 #1

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

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

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

8赞 Dirk Vollmar 3/9/2010 #2

更新:

下面的答案似乎是针对扩展的情况。扩展其他类时,VB 中没有。System.ObjectNullReferenceException

此行为是设计使然,原因如下:

VB 允许您调用在 Object 上定义的扩展方法,但仅限于 如果变量不是静态的 类型化为 Object。

原因是 VB 也支持后期绑定,如果我们绑定到 拨打电话时的分机方法 关闭声明为 Object 的变量, 那么是否模棱两可 您正在尝试呼叫分机 方法或其他后期绑定 方法。

从理论上讲,我们可以用 Strict On 允许这样做,但其中一个 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

评论

0赞 Joel Coehoorn 3/9/2010
我想知道 - 如果扩展方法位于用 C# 编写的库中,vb 是否会以 C# 方式调用扩展方法?
0赞 Dan Tao 3/9/2010
@Joel:我刚刚测试了这个 - 将扩展方法放在C#库中,从VB调用它。VB 代码仍然抛出一个 .Gareth 似乎在做些什么:这个问题只存在于扩展方法应用于而不是更具体的东西时。NullReferenceExceptionSystem.Object
0赞 Gareth Wilson 3/9/2010
事实上,它是你尝试用来调用扩展方法的对象的类型,而不是函数期望的类型。也就是说,IsNull( ByVal Obj as Object) 与 Dim A As String 一起使用 ...A.IsNull 很好,即使 A 是 Nothing。Roygbiv 似乎有相关链接来解释原因。
0赞 Dirk Vollmar 3/9/2010
@Joel Coehoorn:我最初的答案是不正确的,VB 确实以相同的方式调用扩展方法,但在这种特定情况下它根本不支持扩展方法(编译器会创建一个后期绑定调用)。
0赞 Dan Tao 3/9/2010
很棒的,内容丰富的答案。不过,我不得不把它交给 roygbiv,因为我认为他首先解决了基本问题(这是特定于类型的设计决策)。System.Object
3赞 Gareth Wilson 3/9/2010 #3

这似乎是 Object 的一些古怪之处,可能是 VB 中的错误或编译器中的限制,可能需要他的尊者 Jon Skeet 来评论!

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

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

评论

0赞 Laurent Etiemble 3/9/2010
+1.扩展方法仅允许在类型不是 Object 时使用 Nothing。因此,它似乎与运行时对象的性质有关。
14赞 user113476 3/9/2010 #4

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

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

参考:

评论

0赞 Gareth Wilson 3/9/2010
啊,这确实是有道理的......虽然我一直很生气,但 C#/VB 之间的差异/不一致。从这些链接的文章中获取的关键短语是;“当你指定对象时,它的意思是”接受对象以外的任何东西”。棒。8-)
0赞 Dan Tao 3/9/2010
@roygbiv:作为一个不经常用 VB.NET 编写代码的人,老实说,我不知道链接中讨论的后期绑定方法调用是可能的。(我本来会猜到VB。NET的后期绑定能力明显较小。所以谢谢你教了我一些新东西,并成为第一个在这里找到我认为是“真实”答案的人。
1赞 supercat 12/7/2013
@DanTao:我希望 VB.NET 允许将变量声明为,并让它简单地表现为基类型恰好是 的类引用,而无需与 相关的特殊处理。System.ObjectSystem.ObjectObject