Странная проблема с наследованием .Net и видимостью членов - PullRequest
3 голосов
/ 15 июля 2010

У меня проблема с библиотекой классов 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

Это теперь компилируется, но я получаю ошибку во время выполнения - Открытый член 'UpdateTarget' для типа 'TargetManager' не найден.

Итак, я сделал очевидный следующий шаг: 2 UpdateTarget () перегружает Public (вместо Private).

Public Sub UpdateTarget(ByVal Value As TargetOne)

End Sub

Public Sub UpdateTarget(ByVal Value As TargetTwo)

End Sub

Теперь это работает!

Я могу почти понять, почему изменение его на Object сработало бы, но почему эти методы должны быть публичными, когда я вызываю их только из одного класса - я бы предпочел, чтобы они не были доступны за пределами этого класса.

Кто-нибудь может это объяснить?

Заранее спасибо (и извините за длину этого вопроса!)

Дополнительный Спасибо за все ответы до сих пор. У меня есть обходной путь (сделать методы UpdateTarget общедоступными), который заставляет его работать. Другим обходным решением может быть проверка TypeOf для objTarget, а затем DirectCast перед вызовом 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 с Private на Public избавило от ошибки времени выполнения, совершенно вопреки моему пониманию!

Ответы [ 4 ]

5 голосов
/ 15 июля 2010

Мне кажется, что он не может решить, какой метод использовать, потому что вы используете базовый тип обоих параметров метода.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: я могу предложить одну вещь для каждой ситуации(private to public / use of object) скомпилируйте приложение и просмотрите IL с помощью Reflector.По сути, ищите какие-либо различия - возможно, компилятор добавляет что-то для вас изнутри - либо это, либо среда выполнения может определить правильный метод на основе текущего типа.

Обновление 3: думаю, я его получил. Эта цитата по следующей ссылке:

"Объект ранним образом связывается, когда ему назначается переменная, объявленная как конкретный объектtype. "

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

Говорит, что когда вы указываете TargetBase, он рано связывается и компилятор жалуется.Когда вы указываете объект, он связывается с поздним сроком, и среда выполнения жалуется, когда он закрывается по этой ссылке:

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

Поэтому указание общественных работ для вас.Очевидно, что среда выполнения может позднее связывать с корректной перегрузкой - хорошая особенность позднего связывания, которая была скрыта для вас VB.NET: -)

3 голосов
/ 15 июля 2010

Хотя VB не моя специальность, я думаю, что могу правильно ответить на ваши вопросы.

В первой версии вашей программы у вас есть UpdateTarget(objTarget), где objTarget имеет тип TargetBase.VB объясняет это следующим образом:

  • Получатель вызова - «Я».Это имеет известный тип времени компиляции, TargetManager.
  • Аргументом вызова является "objTarget".Это имеет известный тип времени компиляции, TargetBase.
  • Поскольку у получателя и аргументов есть типы, мы должны выполнить разрешение перегрузки, чтобы определить, какую версию UpdateTarget вызывать.
  • ПерегрузкаResolution определяет, что обе версии UpdateTarget требуют потенциально небезопасного преобразования из TargetBase в более конкретный тип.
  • Следовательно, разрешение перегрузки не выполняется.

Во второй версии objTarget имеет тип Object.VB объясняет это следующим образом.

  • Аргумент вызова имеет тип Object.
  • Опять же, разрешение перегрузки не дает нам ничего хорошего.
  • Поскольку разрешение перегрузки не удалось, и что-то относится к типу Object, а Option Strict нет, сгенерируйте код, который снова выполняет анализ во время выполнения с использованием типа аргумента runtime .
  • Для анализа с поздним ограничением необходимо, чтобы вызываемый метод был public .Зачем?Потому что предположим, что этот код был , а не внутри TargetManager.Хотите ли вы иметь возможность вызывать закрытый метод извне TargetManager через позднюю привязку, но не через раннюю привязку?Возможно нет.Это кажется опасным и неправильным.К сожалению, поздняя привязка VB не различает позднюю привязку, выполненную внутри TargetManager, и позднюю привязку, выполненную снаружи .Просто требует, чтобы методы были общедоступными , чтобы их можно было вызывать с поздним ограничением.

В третьей версии мы переходим к позднему связыванию, и позднее связывание успешно выполняется во время выполнения.

Я хотел бы сделать следующее:

  1. Включить опцию строго.

  2. сделать две петли.Да, это не так эффективно, потому что вы делаете цикл два раза, но это большое дело.Если это не самая медленная вещь в вашей программе, то кого это волнует, будет ли она работать на несколько миллисекунд медленнее?

Я не знаю точно, что такое синтаксис VB, но в C # Iнаписал бы так:

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 голосов
/ 15 июля 2010

Как сказал Адам, компилятор не знает, какой метод он должен вызывать. Однако, похоже, что метод UpdateTarget должен быть методом экземпляра, который переопределяет каждый тип цели. Таким образом, вы можете перебирать список и просто вызывать UpdateTarget на objTarget.

Другое преимущество этого в том, что вы лучше инкапсулируете код. TargetManager не нужно знать, что на самом деле делает обновление, только то, что оно должно быть вызвано. Кроме того, в будущем, когда вы пишете TargetThree, вам не нужно менять TargetManager, чтобы иметь возможность обновить новый тип.

1 голос
/ 15 июля 2010

Обновление : поскольку вы указали в комментарии, что в вашем случае нормальный полиморфный подход к этой проблеме невозможен, я бы по крайней мере настоятельно рекомендовал изменить ваш класс 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

У вас полиморфизм задом наперед.

Сразу же, вот как я интуитивно думаю, что вы действительно хотите, чтобы это сработало:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...