代码之家  ›  专栏  ›  技术社区  ›  barrylloyd

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

  •  3
  • barrylloyd  · 技术社区  · 14 年前

    我在一个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 UpDestEngGET() 重载public(而不是private)。

    Public Sub UpdateTarget(ByVal Value As TargetOne)
    
    End Sub
    
    Public Sub UpdateTarget(ByVal Value As TargetTwo)
    
    End Sub
    

    这个现在起作用了!

    我几乎可以理解为什么将其更改为对象会有效,但是为什么当我只从同一个类中调用这些方法时,必须公开这些方法——我宁愿它们在这个类之外不可用。

    有人能解释一下吗?

    提前感谢(对于这个问题的长度很抱歉!)

    附加的 谢谢大家的回答。我得到了使它工作的解决方法(使updateTarget方法公开)。另一个解决方法是对objTarget执行类型检查,然后在调用updateTarget之前执行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 回复  |  直到 14 年前
        1
  •  5
  •   Adam Houldsworth    14 年前

    在我看来,它无法决定使用哪种方法,因为您使用的是两个方法参数的基类型。targetone/two都是有效的targetbase,因此这两种方法在分辨率引擎上看起来都一样,这意味着它无法选择。

    但是,我不知道为什么其他的改变会使它工作…让我想想,更新待定。

    在C我不明白这个问题,因为你不能将TargetBase转发到TargetOne或TargetTwo…它给出了不同的编译器错误-方法的参数无效,因为它无法将基隐式转换为派生的。您提到的第一个编译器错误基本上是与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

    更新: 请注意,我不知道它为什么会突然生效,这听起来像是乔恩·斯基特或埃里克·利珀特的作品。

    更新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为您隐藏:—)

        2
  •  3
  •   Eric Lippert    14 年前

    虽然vb不是我的专长,但我想我能正确回答你的问题。

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

    • 接电话的人是“我”。它有一个著名的编译时类型,targetmanager。
    • 调用的参数是“objTarget”。它有一个著名的编译时类型targetbase。
    • 因为接收者和参数都有类型,所以我们应该进行重载解析来确定要调用哪个版本的UpdateTarget。
    • 重载解析确定两个版本的UpdateTarget都需要从TargetBase到更具体类型的潜在不安全转换。
    • 因此过载分辨率失效。

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

    • 调用的参数属于对象类型。
    • 再说一次,超负荷解决方案在这里给我们带来了什么好处。
    • 由于重载解析失败,并且某个对象类型为“对象”,且“选项严格”未启用,因此生成再次进行分析的代码。 在运行时 使用 运行时 参数的类型。
    • 后期绑定分析要求调用的方法 公众的 . 为什么?因为假设这个代码 在Targetmanager内部。您希望能够通过后期绑定而不是早期绑定从外部TargetManager调用私有方法吗?大概不会。这看起来既危险又错误。不幸的是,vb的后期绑定不能区分后期绑定完成 里面 Targetmanager和延迟绑定完成 外部 . 它只是 requires that the methods be public 为了被称为迟到。

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

    我在这里要做的是:

    1. 打开“选项严格”。

    2. 做两 循环。是的,那没有效率高 因为你做了两次循环,但是, 了不起的事。如果这不是最慢的 在你的节目里,谁在乎呢? 如果有几毫秒 更慢的。

    我不知道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);
    }
    

    又好又简单。进行两次收集,先取出目标,然后取出目标。

    (还要注意,如果我不使用list的任何特性,那么我会将参数设置为IEnumerable,这样方法就更通用了。)

        3
  •  2
  •   unholysampler    14 年前

    正如亚当所说,编译器不知道应该调用哪个方法。但是,这看起来像 UpdateTarget 方法应该是每个目标类型重写的实例方法。这样,您可以迭代列表并简单地调用 更新数据集 objTarget .

    它的另一个优点是可以更好地封装代码。 TargetManager 不需要知道更新实际上做了什么,只需要调用它。此外,当你写 TargetThree 你不必改变 目标管理器 以便能够更新新类型。

        4
  •  1
  •   Dan Tao    14 年前

    更新 :因为您在评论中指出,在您的情况下,这个问题的正常多态方法是不可能的,所以我至少强烈建议您更改 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