.Net继承和成员可见性的奇怪问题

时间:2010-07-15 14:02:48

标签: .net inheritance oop

我在VB.Net类库中遇到了一个问题,我将其大大简化为以下内容......

Public MustInherit Class TargetBase

End Class

Public Class TargetOne
    Inherits TargetBase
End Class

Public Class TargetTwo
    Inherits TargetBase
End Class

Public Class TargetManager
    Public Sub UpdateTargets(ByVal Targets As List(Of TargetBase))
        For Each objTarget As TargetBase In Targets
            UpdateTarget(objTarget)
        Next
    End Sub

    Private Sub UpdateTarget(ByVal Value As TargetOne)

    End Sub

    Private Sub UpdateTarget(ByVal Value As TargetTwo)

    End Sub
End Class

由于UpdateTarget(objTarget)行上的语法错误而无法编译 - 重载决策失败,因为没有缩小转换就无法调用可访问的'UpdateTarget'

所以我将For-Each循环更改为使用Object而不是TargetBase ...

For Each objTarget As Object In Targets
    UpdateTarget(objTarget)
Next

现在编译但是我遇到运行时错误 - 找不到类型'TargetManager'上的公共成员'UpdateTarget'。

所以我采取明显的下一步,使2 UpdateTarget()重载公共(而不是私有)。

Public Sub UpdateTarget(ByVal Value As TargetOne)

End Sub

Public Sub UpdateTarget(ByVal Value As TargetTwo)

End Sub

现在可以了!

我可以理解为什么将它更改为Object会起作用,但为什么当我只在同一个类中调用它们时,这些方法必须公开 - 我宁愿它们在这个类之外也不可用。

有人可以解释一下吗?

提前致谢(对这个问题的篇幅感到抱歉!)

其他 感谢到目前为止每个人的答案。我得到了解决方法(使UpdateTarget方法公共),使其工作。另一种解决方法是在调用UpdateTarget之前对objTarget进行TypeOf检查,然后再调用DirectCast,如...

For Each objTarget As Object In Targets
    If TypeOf objTarget Is TargetOne Then
        UpdateTarget(DirectCast(objTarget, TargetOne))
    ElseIf TypeOf objTarget Is TargetTwo Then
        UpdateTarget(DirectCast(objTarget, TargetTwo))
    End If
Next

这也有用 - 我发布了这个问题,因为我真的很想理解为什么将UpdateTarget的可见性从Private更改为Public来摆脱运行时错误,这完全违背了我的理解!

4 个答案:

答案 0 :(得分:5)

在我看来它无法决定使用哪种方法,因为您使用的是两种方法参数的基本类型。 TargetOne / Two都是有效的TargetBases,因此两种方法看起来与分辨率引擎相同 - 这意味着它无法选择。

但是,我不知道为什么其他更改会使其正常工作......让我想一想,更新等待。

在C#中我没有遇到这个问题,因为你无法将TargetBase转发到TargetOne或TargetTwo ...它给出了不同的编译器错误 - 方法的参数无效,因为它无法隐式地将base转换为derived。您提到的第一个编译器错误基本上是VB.NET等效的。

我发现了这个链接,但我不确定它是用于VB还是VB.NET - 无论哪种方式,有趣的阅读: http://msdn.microsoft.com/en-us/library/tb18a48w.aspx

这也可能与Option Strict和VB.NET 2010中的协方差有关。本文有一些重载部分,可能证明有用: http://msdn.microsoft.com/en-us/magazine/ee336029.aspx

更新:请注意,我不知道为什么它突然起作用,这听起来像Jon Skeet或Eric Lippert。

更新2:我可以建议的一件事是针对每种情况(私有对公共/使用对象)编译应用程序并使用Reflector查看IL。基本上,寻找任何差异 - 可能是编译器正在为您添加一些内容 - 或者说,或者运行时能够根据当前类型确定正确的方法。

更新3:认为我明白了。此引用来自以下链接:

  

“对象是早期绑定的   分配给声明为的变量   特定对象类型。“

http://visualbasic.about.com/od/usingvbnet/a/earlybind.htm

说当你指定TargetBase时,它是早期绑定和编译器抱怨。当你指定对象时,它是后期绑定的,运行时会在其私有链接时抱怨:

http://msdn.microsoft.com/en-us/library/h3xt2was(VS.80).aspx

因此,为您指定公共工程。运行时显然能够延迟绑定到正确的重载 - 这是VB.NET为您隐藏的后期绑定的一个很好的特性: - )

答案 1 :(得分:3)

虽然VB不是我的专长,但我认为我可以正确回答你的问题。

在程序的第一个版本中,您有UpdateTarget(objTarget),其中objTarget的类型为TargetBase。 VB原因如下:

  • 通话的接收者是“我”。它有一个众所周知的编译时类型TargetManager。
  • 电话的参数是“objTarget”。它有一个众所周知的编译时类型TargetBase。
  • 由于接收者和参数都有类型,我们应该进行重载决策以确定要调用哪个版本的UpdateTarget。
  • 重载决策确定两个版本的UpdateTarget都需要从TargetBase到更具体类型的潜在不安全转换。
  • 因此,重载决策失败。

在第二个版本中,objTarget的类型为Object。 VB原因如下。

  • 调用的参数是Object类型。
  • 同样,重载决议在这里没有给我们任何好处。
  • 由于重载决策失败并且某些内容属于Object类型,并且Option Strict未启用,因此使用运行时类型生成在运行时再次进行分析的代码论证。
  • 后期分析要求名为 public 的方法。为什么?因为假设此代码在TargetManager中。您是否希望能够通过后期绑定从外部TargetManager调用私有方法,而不是通过早期绑定?可能不是。这似乎是危险和错误的。不幸的是,VB的后期绑定并没有区分后期绑定内部 TargetManager和后期绑定外部。它只是requires that the methods be public才能被称为后期绑定。

在第三个版本中,我们进入后期绑定,后期绑定在运行时成功。

我在这里要做的是:

  1. 启用选项严格。

  2. 做两个 循环。是的,那不是那么有效 因为你做了两次循环,但是, 很重要。如果这不是最慢的 在你的程序中然后谁在乎 如果它得到几毫秒 慢。

  3. 我不确切知道VB语法是什么,但在C#中我会这样写:

    public void UpdateTargets(IEnumerable<TargetBase> targets) 
    {
        foreach(var targetOne in targets.OfType<TargetOne>())
            UpdateTarget(targetOne);
        foreach(var targetTwo in targets.OfType<TargetTwo>())
            UpdateTarget(targetTwo);
    }
    

    美好而简单。完成两次收集,首先取出TargetOnes,然后取出TargetTwos。

    (另请注意,如果我没有使用List的任何功能,那么我将改为使用IEnumerable参数,以便该方法变得更加通用。)

答案 2 :(得分:2)

正如Adam所说,编译器不知道它应该调用哪种方法。但是,这似乎UpdateTarget方法应该是每个Target类型重写的实例方法。这样,您就可以遍历列表,只需在UpdateTarget上调用objTarget

这样做的另一个好处是您可以更好地封装代码。 TargetManager不需要知道更新实际执行的操作,只需要调用它即可。此外,当您撰写TargetThree时,您无需更改TargetManager即可更新新类型。

答案 3 :(得分:1)

更新:由于您在评论中指出在您的情况下无法解决此问题的正常多态方法,因此我至少强烈建议您将TargetManager类更改为有一个UpdateTarget方法接受TargetBase参数。然后检查该方法中的相应类型。这可以防止出现......的潜在问题。

If TypeOf x Is A Then
    DoSomething(DirectCast(x, A))
ElseIf TypeOf x Is B Then
    DoSomething(DirectCast(x, B))
End If

......到处都是。

换句话说,把丑陋的支票放在一个地方:

Public Class TargetManager
    Public Sub UpdateTarget(ByVal target As TargetBase)
        Dim t1 = TryCast(target, TargetOne)
        If t1 IsNot Nothing Then
            UpdateTargetOne(t1)
            Return
        End If

        Dim t2 = TryCast(target, TargetTwo)
        If t2 IsNot Nothing Then
            UpdateTargetTwo(t2)
            Return
        End If
    End Sub

    ' I would also recommend changing the targets parameter type here '
    ' to IEnumerable(Of TargetBase), as that is all you need to do '
    ' a For Each loop. '
    Public Sub UpdateTargets(ByVal targets As IEnumerable(Of TargetBase))
        For Each objTarget As TargetBase In Targets
            UpdateTarget(objTarget)
        Next
    End Sub

    Private Sub UpdateTargetOne(ByVal target As TargetOne)
        ' Do something. '
    End Sub

    Private Sub UpdateTargetTwo(ByVal target As TargetTwo)
        ' Do something. '
    End Sub
End Class

你已经倒退了polymorphism

立即开始,这就是我直觉地认为你真的想让它发挥作用的方式:

Public MustInherit Class TargetBase
    Protected Friend MustOverride Sub Update()
End Class

Public Class TargetOne
    Inherits TargetBase

    Protected Friend Overrides Sub Update()
    End Sub
End Class

Public Class TargetTwo
    Inherits TargetBase

    Protected Friend Overrides Sub Update()
    End Sub
End Class

Public Class TargetManager
    Public Sub UpdateTargets(ByVal Targets As List(Of TargetBase))
        For Each objTarget As TargetBase In Targets
            objTarget.Update()
        Next
    End Sub
End Class