2010-07-15 58 views
3

我有我大大簡化到以下的VB.Net類庫一個問題...與.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循環使用的對象,而不是TargetBase ...

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

這現在編譯,但我得到一個運行時錯誤 - 未找到類型'TargetManager'上的公共成員'UpdateTarget'。

所以我採取了明顯的下一步使重載公開(而不是私有)。

Public Sub UpdateTarget(ByVal Value As TargetOne) 

End Sub 

Public Sub UpdateTarget(ByVal Value As TargetTwo) 

End Sub 

This now works!

我可以理解爲什麼將它改爲Object可行,但爲什麼只有在同一個類中調用它們時,才能將這些方法設爲Public? - 我寧願它們在這個類之外不可用。

有人可以解釋一下嗎?

在此先感謝(和抱歉這一問題的長度!)

附加 感謝大家的答案爲止。我有解決方法(使UpdateTarget方法公開),使其工作。另一種解決辦法是做objTarget然後DirectCast一個TypeOf運算檢查調用UpdateTarget,像以前一樣......

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的知名度私下公開擺脫運行時錯誤,完全違揹我的理解!

+0

您的意思是結構化你的UpdateTarget方法,如這個?您是不是想要在每個TargetBase子類中都有一個UpdateTarget方法?然後你可以調用objTarget.UpdateTarget。你之前可能得到了這個編譯錯誤,因爲.net不知道它傳遞了哪個類(縮小轉換) – jrsconfitto 2010-07-15 14:08:25

+0

只是出於好奇「這現在起作用」的含義 - 它調用TargetBase類型的相關方法(即正確);或者它總是調用其中一種方法? – 2010-07-15 14:16:02

+0

@Jugglingnutcase - 謝謝你的回答。我已經考慮過了,不幸的是這是不可能的。我展示的代碼示例非常簡單(只是爲了說明問題) - 在我的實際項目中,我無法訪問TargetBase,因此無法向其添加UpdateTarget方法。 – barrylloyd 2010-07-15 14:24:07

回答

5

在我看來,它不能決定使用什麼方法,因爲您正在使用這兩種方法參數的基本類型。 TargetOne/Two都是有效的TargetBase,所以這兩種方法看起來都與解析引擎相同 - 意味着它不能選擇。

但是,我不知道爲什麼其他更改使它工作...讓我想想,更新掛起。

在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

更新:請注意,我不知道爲什麼突然其工作,這聽起來像一個喬恩斯基特或埃裏克利珀。

更新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爲你隱藏的後期綁定的一個很好的功能:-)

+0

謝謝Adam。您的超載解析鏈接非常豐富。我猜我的代碼在第1點上失敗)可訪問性 - 但我不明白爲什麼 – barrylloyd 2010-07-15 14:37:30

+0

@barrylloyd我相信它與編譯器一起失敗 - 但它如何成功是一個謎。編譯器/運行時必須爲你做一些事情 - 也許回顧IL會產生一些結果。 – 2010-07-15 14:42:11

+0

謝謝Adam。從你的更新3我得到http://msdn.microsoft.com/en-us/library/0tcf61s1(v=VS.80).aspx有一個黃色的說明,說...「晚綁定只能使用訪問聲明爲Public的類型成員。訪問聲明爲好友或受保護好友的成員會導致運行時錯誤'。 – barrylloyd 2010-07-15 16:01:43

2

正如Adam所說,編譯器不知道它應該是哪種方法呼叫。但是,這看起來像UpdateTarget方法應該是每個Target類型覆蓋的實例方法。通過這種方法,您可以遍歷列表,並簡單地撥打電話UpdateTargetobjTarget

這樣做的另一個好處是你更好地封裝了代碼。 TargetManager不需要知道更新實際上做了什麼,只是需要調用它。此外,當您編寫TargetThree時,您不必更改TargetManager以便能夠更新新類型。

+0

感謝您的回答。我已經考慮過了,不幸的是這是不可能的。我展示的代碼示例非常簡單(只是爲了說明問題) - 在我的實際項目中,我無法訪問TargetBase,因此無法向其添加UpdateTarget方法。 – barrylloyd 2010-07-15 14:23:28

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 
+0

OP瞭解通常的做法,我認爲這個問題更多的是它開始自動工作的奇怪情況。 – 2010-07-15 14:34:27

+0

感謝Dan - Adam說的是正確的 – barrylloyd 2010-07-15 14:35:33

+0

感謝Dan,關於重構代碼的所有好建議以及很好的替代解決方法。 – barrylloyd 2010-07-15 14:58:26

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. 做兩個 循環。是的,那不是效率很高 因爲你做了兩次循環,但是, 大交易。如果這不是你程序中最慢的 ,那麼如果它減慢幾個毫秒 ,誰會在意 。

我不知道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。

(還要注意的是,如果我不使用名單中的任何功能,那麼我會做的說法,而不是IEnumerable的,因此該方法變得更加普遍。)

+0

+1大聲笑第一次去正確的 - 我花了一些時間谷歌搜索得出的結論,即使如此,我不能說得這麼雄辯! VB.NET不是我的強大套件 - 我只是知道如何做基礎知識。我今天學到的東西是隱含的後期約束 - 正如我再次學到的是解決方案的根源。 – 2010-07-15 16:06:05