【问题标题】:Overload resolution works for normal method but not for constructor重载解析适用于普通方法,但不适用于构造函数
【发布时间】:2021-04-02 19:16:44
【问题描述】:

我的目标是进行一系列重载,根据参数的类型(仅在运行时知道)调用正确版本的方法。但是,在我要重载的方法是构造函数的情况下,我遇到了一个有趣的问题。

采用如下继承结构:

Public MustInherit Class A
    Public Property Common As String
End Class

Public Class X
    Inherits A

    Public Property Unique1 As String
    Public Property Unique2 As String
End Class

Public Class Y
    Inherits A

    Public Property Unique3 As String
    Public Property Unique4 As String
End Class

基类AXY 继承。

现在学习这门课,我将用它来展示问题:

Public Class Foo
    Public Sub New(v As X)
        Common = v.Common
        Prop1 = v.Unique1
        Prop2 = v.Unique2
        Prop3 = "Some value"
        Prop3 = String.Empty
    End Sub

    Public Sub New(v As Y)
        Common = v.Common
        Prop1 = "Some value"
        Prop2 = String.Empty
        Prop3 = v.Unique3
        Prop4 = v.Unique4
    End Sub

    Public ReadOnly Property Common As String
    Public ReadOnly Property Prop1 As String
    Public ReadOnly Property Prop2 As String
    Public ReadOnly Property Prop3 As String
    Public ReadOnly Property Prop4 As String

    Public Shared Sub Bar(v As X)
    End Sub

    Public Shared Sub Bar(v As Y)
    End Sub
End Class

有一个带有重载的普通方法Bar,还有一个带有重载的构造函数New。第一个New与第一个Bar具有相同的签名,第二个New与第二个Bar具有相同的签名。

最后拿下这个测试代码:

Public Sub Test()
    Dim Param As Object = New X

    'This works fine
    Foo.Bar(Param)

    'This gives a compile error
    Dim Thing As New Foo(Param)
End Sub

编译器对Bar的调用似乎没有问题,但是对于构造函数调用我得到以下编译错误:

重载解析失败,因为没有缩小转换就无法调用可访问的“新建”:
'Public Sub New(v As X)':参数匹配参数 'v' 从 'Object' 缩小到 'X'。
'Public Sub New(v As Y)':参数匹配参数 'v' 从 'Object' 缩小到 'Y'。

为什么构造函数调用会导致错误,而对 Bar 的调用不会。

另外,如果我将Param 声明更改为Dim Param As A = New X,那么它们都不会编译。

我觉得我应该理解这一点,但无论出于何种原因我都没有。有人可以告诉我为什么这不起作用,并可能提出解决方法吗?

【问题讨论】:

  • 你有Option Strict Off,设置On会更清晰。 Dim Param As New X() 对两者都有效。
  • @Jimi Dim Param As New X 会起作用,但这没有抓住重点。 “我的目​​标是进行一系列重载,根据参数的类型调用正确版本的方法(仅在运行时知道)。”
  • 然后实现一个接口而不是从一个抽象类继承。您还将获得泛型支持。拥有Option Strict 支持的意义在于,您会看到两个构造在编译时都失败了,所以您不需要问自己为什么一个失败?,因为两者实际上都失败了,这是只是暂时对你隐藏。
  • @Jimi 两对都失败了。 Bar 重载按预期工作。 Dim Param As Object = New X 导致Bar(v As X) 被调用,Dim Param As Object = New Y 导致Bar(v As Y) 被调用。只有构造函数重载会产生任何错误。
  • 对不起,Option Strict Off 的问题不仅在于它是Off(这直接暗示了所有这些),而是您在构建应用程序时考虑到了一种松散类型的模型,因此,太多的代码部分依赖于这个非特性(为简化 VB6 迁移而设计),试图忽略您正在使用的强类型框架。这会生成只是隐藏问题的代码。 -- 你正在尝试构建一个通用结构,但你不想谈论接口。 .Net 为此使用接口,而不是 Object 类型,并且继承仅限于单个类。

标签: vb.net overloading overload-resolution constructor-overloading


【解决方案1】:

虽然仍不清楚您要实现的具体目标,但 Answer 是共享代码的唯一合理场所。这是解决Option Strict On 问题的尝试,使用接口定义类必须具有的属性,以便传递给Foo 进行构造。

注意代码中的 cmets 也有助于解释。

这将事物抽象化,以便Foo 不必知道从A 派生的所有类型——它只知道接口。实际上,它“反转”了关系,以便 A 及其派生类型知道 Foo (根据接口)需要什么。其余的是 X 和 Y 的实现,其中 Props 1 到 4 的定义现在存在(而不是在各种重载的 Foo 构造函数中)。这会将Foo 的构造函数数量减少到只有一个。

将派生自A 的类的属性转换为Foo 的属性的逻辑必须存在于某处。通过将这个逻辑从Foo 中推出,而是放到派生类中,您可以避免Option Strict Off 推迟到运行时的缩小问题。此外,添加从A 派生的新Z 类很容易,您无需修改​​Foo 即可立即使用它。

但同样,由于您不完全清楚您打算如何处理此示例代码,因此很难知道这种方法是否“适用”于您的想法。

Option Strict On

Module Module1
    Sub Main()
        Dim Param As A = New X
        Dim Thing As New Foo(Param)

        Param = New Y
        Thing = New Foo(Param)

        'if you make a new class Z which Inherits A, it will immediately be translatable to Foo
        'albeit with all String.Empty properties unless you override the properties from A
    End Sub

End Module

'Defines what a Foo wants, what a Foo needs
Public Interface IPropertiesForFoo
    ReadOnly Property Common As String
    ReadOnly Property Prop1 As String
    ReadOnly Property Prop2 As String
    ReadOnly Property Prop3 As String
    ReadOnly Property Prop4 As String
End Interface

Public MustInherit Class A
    Implements IPropertiesForFoo

    Public Property Common As String


#Region "IPropertiesForFoo implementation"
    'these are Overridable, so derived classes can choose what to change and what not to
    'note these are all Protected, so only derived classes know about them.  Users of A may not care.

    'This is just one choice;
    ' you could also use Throw New NotImplementedException (instead of Return String.Empty)
    ' and force derived classes to handle every property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop1 As String Implements IPropertiesForFoo.Prop1
        Get
            Return String.Empty
        End Get
    End Property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop2 As String Implements IPropertiesForFoo.Prop2
        Get
            Return String.Empty
        End Get
    End Property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop3 As String Implements IPropertiesForFoo.Prop3
        Get
            Return String.Empty
        End Get
    End Property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop4 As String Implements IPropertiesForFoo.Prop4
        Get
            Return String.Empty
        End Get
    End Property

    'private, and doesn't need to be Overridable, as Common can map directly
    Private ReadOnly Property IPropertiesForFoo_Common As String Implements IPropertiesForFoo.Common
        Get
            Return Common
        End Get
    End Property
#End Region
End Class

Public Class X
    Inherits A

    Public Property Unique1 As String
    Public Property Unique2 As String

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop1 As String
        Get
            Return Unique1
        End Get
    End Property

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop2 As String
        Get
            Return Unique2
        End Get
    End Property

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop3 As String
        Get
            Return "Some value"
        End Get
    End Property

    'doesn't need to override Prop4; leave it as String.Empty
End Class

Public Class Y
    Inherits A

    Public Property Unique3 As String
    Public Property Unique4 As String

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop1 As String
        Get
            Return "Some value"
        End Get
    End Property

    'doesn't need to override Prop2; leave it as String.Empty

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop3 As String
        Get
            Return Unique3
        End Get
    End Property

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop4 As String
        Get
            Return Unique4
        End Get
    End Property
End Class


Public Class Foo

    Public Sub New(v As IPropertiesForFoo)
        Common = v.Common
        Prop1 = v.Prop1
        Prop2 = v.Prop2
        Prop3 = v.Prop3
        Prop4 = v.Prop4
    End Sub

    Public ReadOnly Property Common As String
    Public ReadOnly Property Prop1 As String
    Public ReadOnly Property Prop2 As String
    Public ReadOnly Property Prop3 As String
    Public ReadOnly Property Prop4 As String


End Class

就此而言,取决于Foo 的其余部分实际上做了什么,您甚至可能不需要Foo - 只需传递您的A 实例,因为它们也是IPropertiesForFoo。然后根据需要拉出它们标记为 Prop1、Prop2 的属性。 (同样,您的简化示例源并没有暗示更大的上下文足以知道这种方法是否适合。)

【讨论】:

    【解决方案2】:

    我相信我会再次让很多人告诉我,我不应该这样做,但这是我实际解决问题的方法:

    Public Class Foo
        Public Sub New(v As X)
        End Sub
    
        Public Sub New(v As Y)
        End Sub
    
        Public Shared Function Create(v As X) As Foo
            Return New Foo(v)
        End Function
    
        Public Shared Function Bar(v As Y) As Foo
            Return New Foo(v)
        End Function
    End Class
    

    这让我可以像这样使用Foo

    Dim Param As Object = New Y
    Foo.Create(Param)
    

    是的,上面使用了后期绑定和松散类型的代码。但它也将冗余代码保持在最低限度,不需要冗长的接口定义或实现,仍然是完全可预测的,并且完全符合我的要求。我确实认为在正确的上下文中使用此类功能很有用且有效。

    我仍然希望我能得到一些答案,说明为什么重载决议适用于普通方法而不适用于构造函数。但现在我想我只能接受这个变通办法了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多