【问题标题】:Extension methods declared on Object need one more parameter than others?在 Object 上声明的扩展方法需要比其他方法多一个参数?
【发布时间】:2015-09-08 16:26:58
【问题描述】:

对象can be declared on Object but cannot be used like obj.ExtMethod() 的扩展方法。这是设计使然。另一方面,也可以使用任何扩展方法,如ExtMethod(obj)为什么在 Object 上声明的扩展方法的调用与在其他类型上声明的扩展方法不同? 我正在寻找这背后的逻辑。还是bug?

要发现差异,请参见下面的示例并将普通的ToString1()ToString2()/ToString3() 进行比较。

Imports System.Runtime.CompilerServices

Module CompilerExtensionsModule

    ' standard one, works as expected
    <Extension>
    Function ToString1(value As Integer) As String
        Return value.ToString()
    End Function

    ' obj isn't expected as parameter on actual usage, context is supplied instead
    <Extension>
    Function ToString2(obj As Object) As String
        Return If(obj Is Nothing, "", obj.ToString())
    End Function

    ' this is way how to have obj as parameter - first parameter is ignored
    <Extension>
    Function ToString3(base As Object, obj As Object) As String
        Return If(obj Is Nothing, "", obj.ToString())
    End Function

    ' let's try with something different than Object
    <Extension>
    Function ToStringClass1(obj As Class1) As String
        Return obj.ToString()
    End Function

End Module

课堂使用:

ToString1(3)    ' as expected - 1 parameter declared, 1 expected
ToString2()     ' 1 parameter declared, no parameters expected in call
ToString3(Nothing) ' 2 parameters declared, 1 expected in call - passed as second parameter

添加细节:(至少完整的工作示例 - 3 个文件 - 包括上述一个)

完整的调用上下文:

Public Class Class1

    Sub Action1()
        Dim value1 As Integer = 1
        Dim obj1 As Object = Nothing
        Dim obj2 As New Class1

        Console.WriteLine(ToString1(value1))
        Console.WriteLine(ToString2())
        Console.WriteLine(ToString3(obj1))
        Console.WriteLine(ToStringClass1())
    End Sub

End Class

完整的调用上下文:Class 不同于 Class1 – 没有发布有问题的问题,但效果很奇怪:

Public Class Class2

    Sub Action1()
        Dim value1 As Integer = 1
        Dim obj1 As Object = Nothing
        Dim obj2 As New Class1

        Console.WriteLine(ToString1(value1))
        Console.WriteLine(ToString2())
        Console.WriteLine(ToString3(obj1))
        Console.WriteLine(ToStringClass1(obj2))

        obj2.ToString2()
        ToString2(obj2) ' INVALID - won't compile in any class (but will do in any module)
        ToString3(obj2) ' EDIT: VALID because two parameters are actually supplied here

        ' EDIT (see comments below the answer):
        CompilerExtensionsModule.ToString2(obj2) ' VALID - switching the context solves it
        ' Note: for ext.mehods of Object, this form of call is needed in any class
        ' Reason: any class is descendant of Object => VB wants to supply 1st parameter
        '    in calling context of class => use calling context of ext.module instead

    End Sub

End Class

完整的调用上下文:模块 – 没有问题:

Module Module1

    Sub Main()
        Dim value1 As Integer = 1
        Dim obj1 As Object = Nothing
        Dim obj2 As New Class1

        Console.WriteLine(ToString1(value1))
        Console.WriteLine(ToString2(obj1))
        Console.WriteLine(ToString3(obj1, obj1))
        Console.WriteLine(ToStringClass1(obj2))

        ' unlike in Class2, no issues here:
        obj2.ToString2()
        ToString2(obj2)

    End Sub

End Module

【问题讨论】:

  • 请展示一个简短但完整的示例来说明问题。 (您的模块目前甚至无法为我编译。)我的 猜测 是您隐含地将 Me 作为第一个参数传递..
  • @JonSkeet - 我的道歉 - 已修复...让我们现在删除这些 cmets,因为不再需要它们。
  • ToString3 中你不使用第一个参数 - 为什么有它?
  • @OneFineDay - 我不希望它在那里,请查看ToString3() 在课堂上调用时的示例。

标签: vb.net call extension-methods roslyn option-strict


【解决方案1】:

如果存在 Option Strict On,为什么在 Object 上声明的扩展方法的调用与在其他类型上声明的扩展方法不同?

因为您的调用上下文(您没有显示)不能转换为Integer,但可以转换为Object。想象一下,您正在明确调用:

Me.ToString2()
Me.ToString3(Nothing)

这是转换为:

ToString2(Me)
ToString3(Me, Nothing)

不会发生在ToString1(被视为常规共享模块范围的方法),因为Me 不能隐式转换为Integer。 (我不知道VB中方法调用的细节,但听起来扩展方法是在以常规方式调用模块范围的共享方法之前搜索的。)

【讨论】:

  • 指向调用上下文是绝对正确的。当在类中以ExtMethod(obj) 的形式而不是obj.ExtMethod 调用它们时,它似乎作为扩展方法的第一个参数自动提供。此外,这可以通过 all 扩展方法重现,而不仅仅是在 Object 上声明的那些。也许您可以相应地更新答案(如果您认为需要基于此更新),我可以接受。
  • 我认为不需要任何更新 - 在实例成员的上下文中,对 Foo 的调用被隐式地尝试为 Me.Foo()。顺便说一句,C# 不能那样工作——你需要调用 this.ToString2。这对于 C# 6 可能已经改变,或者可能只是针对 C# 7 进行讨论 - 不确定。
  • 我验证了不同的调用上下文(他们确认了你的答案),老实说,这个“功能”让我非常惊讶。在框架的其他地方,我从未见过如此不一致的“自动传递第一个参数”。如果感觉很不自然。更糟糕的是,我需要 两种方法来实现相同的功能: obj2.ToString2() 加上 ToString3(obj2) 可能没有办法解决这个问题。
  • @miroxlav:我不明白你为什么需要两种方法。如果您调用obj.ToString3(),它不会隐式传递Me。 (仅当您以不合格的方式使用该方法时才会发生这种情况。)您认为为什么需要两种方法?
  • 我想我清楚地理解了你的意图,但看起来Me 在任何情况下都被隐式传递了。请参阅上面Class2 示例中“后果”注释下方的行。我可以把它放到 any 类中,并且上下文仍然作为第一个参数传递。 (只有模块表现正常。)唯一的解决方法是obj2.ToString2() 加上ToString3(obj2),因此需要两次具有相同目的的方法。哦,如果您将方法更改为SharedMe 不会通过,但编译器无论如何都会抛出错误。看起来像死路一条。
猜你喜欢
  • 1970-01-01
  • 2015-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-13
相关资源
最近更新 更多