"Тени" и "Переопределения" в VB.NET

Какое значение имеют два ключевых слова Shadows и Overrides? Что они делают и для какого контекста тот или иной предпочтительнее?

16 ответов

Решение

Я бы не стал рассматривать Shadows как концепцию ООП. Переопределение означает, что вы предоставляете новую или дополнительную функциональность для метода / свойства и т. Д., Которые были объявлены в классе предка. Shadows действительно заставляет компилятор думать, что родительский метод / свойство и т. Д. Даже не существует.

Я не пользуюсь тенями. Придерживайтесь переопределений. Эти полезные маленькие "особенности", которые VB предоставляет годами, всегда в какой-то момент вызывают у вас горе.

Переопределения - более нормальный классификатор. Если дочерний класс переопределяет функцию базового класса таким образом, то независимо от того, как на дочерний объект ссылаются (используя базовый класс или ссылку на дочерний класс), вызывается дочерняя функция.

С другой стороны, если функция дочернего класса затеняет функцию базового класса, то дочерний объект, доступ к которому осуществляется через ссылку на базовый класс, будет использовать эту функцию базового класса, несмотря на то, что он является дочерним объектом.
Определение дочерней функции используется только в том случае, если доступ к дочернему объекту осуществляется с использованием соответствующей дочерней ссылки.

Слежение, вероятно, не делает то, что вы думаете.

Рассмотрим следующие классы:

Public MustInherit Class A 
    Public Function fX() As Integer
        Return 0
    End Function
End Class

Public Class B
    Inherits A 
    Public Shadows Function fX() As Integer
        Return 1
    End Function 
End Class

Теперь я использую их:

Dim oA As A
Dim oB As New B
oA = oB

Вы, наверное, думаете, что oA и oB одинаковы, верно?

Нету.

oA.fx = 0, а oB.fx = 1

Имхо, это очень опасное поведение, и оно едва упоминается в документации.

Если бы вы использовали переопределение, они были бы одинаковыми.

Таким образом, несмотря на то, что существуют законные способы использования теней, есть вероятность, что то, что вы делаете, не относится к их числу, и этого следует избегать.

Переопределения - расширение или создание альтернативных функций для метода.

Пример: добавление или расширение функциональности события Paint окна.


    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e) ' retain the base class functionality
        'add code for extended functionality here
    End Sub

Shadows - переопределяет унаследованный метод и принудительно использует его для всех классов, экземпляров которых содержит этот тип. Другими словами, метод не перегружен, а переопределен, а методы базового класса недоступны, что вынуждает использовать функцию, объявленную в классе. Shadows сохраняет или сохраняет определение метода таким образом, чтобы оно не уничтожалось при изменении методов базового класса.

Пример: вынудите все классы "B" использовать свое странное определение Add, чтобы при изменении методов Add класса A это не влияло на добавление B. (Скрывает все методы Add базового класса. Не сможет вызвать A.Add(x, y, z) из экземпляра B.)


    Public Class A
        Public Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
            Return x + y
        End Function
        Public Function Add(ByVal x As Integer, ByVal y As Integer, ByVal z As Integer) As Integer
            Return x + y + z
        End Function
    End Class
    Public Class B
        Inherits A
        Public Shadows Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
            Return x - y
        End Function
    End Class

Иногда маленький пример действительно помогает понять разницу технически.

Sub Main()

    Dim o As New ChildClass

    Console.WriteLine(o.GetValOverride()) ' Prints 2
    Console.WriteLine(o.GetValShadow()) ' Prints 2
    Console.WriteLine(CType(o, ParentClass).GetValOverride()) ' Prints 2
    Console.WriteLine(CType(o, ParentClass).GetValShadow()) ' Prints 1
    Console.ReadLine()

End Sub

Class ParentClass

    Public Overridable Function GetValOverride() As String
        Return "1"
    End Function

    Public Function GetValShadow() As String
        Return "1"
    End Function

End Class

Class ChildClass
    Inherits ParentClass

    Public Overrides Function GetValOverride() As String
        Return "2"
    End Function

    Public Shadows Function GetValShadow() As String
        Return "2"
    End Function

End Class

Ключевое слово "shadows" по сути говорит: "Если кто-то обращается к этому объекту, знает, что он принадлежит к этому типу или одному из его потомков, используйте этот элемент, в противном случае используйте базовый". Простейшим примером этого может быть базовый класс ThingFactory, который включает в себя метод "MakeNew", который возвращает Thing, и класс CarFactory, производный от ThingFactory, метод которого "MakeNew" всегда возвращает Thing, который будет иметь производный тип Car. Если подпрограмма знает, что ThingFactory, которой она владеет, происходит, в частности, CarFactory, то она будет использовать затененный CarFactory.MakeNew (если он существует), который может указать тип возвращаемого значения как Car. Если подпрограмма не знает, что ее ThingFactory на самом деле является CarFactory, она будет использовать не затененный MakeNew (который должен вызывать внутренний защищенный переопределяемый метод MakeDerivedThing).

Кстати, еще одно хорошее использование теней - это предотвращение доступа производных классов к защищенным методам, которые больше не будут работать. Невозможно просто скрыть член от производных классов, кроме назначения нового, но можно запретить производным классам делать что-либо с защищенным членом, объявив новый защищенный пустой класс с этим именем. Например, если вызов MemberwiseClone для объекта сломает его, можно объявить:

  Класс Защищенных Теней MemberwiseClone
  Конечный класс
Обратите внимание, что это не нарушает принципы ООП, такие как принцип замещения Лискова, поскольку это применимо только в тех случаях, когда производный класс может использоваться вместо объекта базового класса. Если Foo и Bar наследуются от Boz, метод, который принимает параметр Boz, может быть законно передан в Foo или Bar. С другой стороны, объект типа Foo будет знать, что его объект базового класса имеет тип Boz. Это никогда не будет ничем другим (например, это гарантированно не будет Баром).

Пример теневого копирования: предположим, что вы хотите использовать функцию в стороннем компоненте, но функция защищена. Вы можете обойти это ограничение с помощью простого наследования и предоставления скрытой функции, которая в основном вызывает ее базовую функцию:

Public Class Base

    Protected Sub Configure()
        ....
    End Sub

End Class

Public Class Inherited
    Inherits Base

    Public Shadows Sub Configure()
        MyBase.Configure()
    End Sub

End Class

Я думаю, что на самом деле есть два сценария, которые люди принимают здесь, и оба являются законными. Вы могли бы по-настоящему разбить их на дизайнера базового класса и разработчика спустя годы, который реализует подкласс, который не может модифицировать базовый класс. Так что да, лучшее, что нужно сделать, это переопределить, если у вас есть такая роскошь. Это чистый подход OOD.

С другой стороны, у вас может быть что-то вроде приведенного выше примера, где вы на другом конце этого уравнения должны реализовать подкласс, и вы не можете изменить тот факт, что метод, который вам нужно переопределить, не помечен как перезаписываемый. Возьмите например

Public Shadows Function Focus() As Boolean
    txtSearch.Focus()
    Return MyBase.Focus()
End Function

В этом случае я наследую свой класс от класса управления Winform, который, к сожалению, не помечен как перезаписываемый. На данный момент я сталкиваюсь с тем, чтобы сделать код "чистым" или сделать его более понятным. Клиент этого элемента управления просто хочет вызвать control.Focus() и, возможно, ему все равно. Я мог бы назвать этот метод FocusSearchText() или Focus2 и т. Д., Но я считаю, что вышеизложенное намного проще для клиентского кода. Это правда, что если клиент затем использует этот элемент управления в качестве базового класса и вызывает Focus, мой код не будет оправдан. Но это довольно далеко.

В конце концов, все сводится к решению суда, и вам придется его сделать.

Это недавняя ссылка MSDN: различия между теневым копированием и переопределением

Затенение защищает от последующей модификации базового класса, которая вводит члена, который вы уже определили в своем производном классе. Обычно вы используете теневое копирование в следующих случаях:

** Вы ожидаете, что ваш базовый класс может быть изменен для определения элемента с тем же именем, что и у вас. *

** Вам нужна свобода изменения типа элемента или последовательности вызовов. *

(Мне еще предстоит исследовать использование в отношении объема и типов)

Ну вот ответ по коду.

Module Module1

    Sub Main()
        Dim object1 As Parent = New Child()
        Console.WriteLine("object1, reference type Parent and object type Child")
        object1.TryMe1()
        object1.TryMe2()
        object1.TryMe3()

        Console.WriteLine("")
        Console.WriteLine("")
        Console.WriteLine("object2, reference type Child and object type Child")
        Dim object2 As Child = New Child()

        object2.TryMe1()
        object2.TryMe2()
        object2.TryMe3()

        Console.ReadLine()
    End Sub

End Module

Public Class Parent

    Public Sub TryMe1()
        Console.WriteLine("Testing Shadow: Parent.WriteMe1")
    End Sub

    Public Overridable Sub TryMe2()
        Console.WriteLine("Testing override: Parent.WriteMe2")
    End Sub

    Public Sub TryMe3()
        Console.WriteLine("Testing Shadow without explicitly writing shadow modifier: Parent.WriteMe3")
    End Sub
End Class

Public Class Child
    Inherits Parent

    Public Shadows Sub TryMe1()
        Console.WriteLine("Testing Shadow: Child.WriteMe1")
    End Sub

    Public Overrides Sub TryMe2()
        Console.WriteLine("Testing override: Child.WriteMe2")
    End Sub

    Public Sub TryMe3()
    Console.WriteLine("Testing Shadow without explicitly writing shadow modifier: Child.WriteMe3")
    End Sub
End Class


'Output:
'object1, reference type Parent and object type Child
'Testing Shadow: Parent.WriteMe1
'Testing override: Child.WriteMe2
'Testing Shadow without explicitly writing shadow modifier: Parent.WriteMe3


'object2, reference type Child and object type Child
'Testing Shadow: Child.WriteMe1
'Testing override: Child.WriteMe2
'Testing Shadow without explicitly writing shadow modifier: Child.WriteMe3

Вы можете скопировать вставить и попробовать это самостоятельно. Как вы можете видеть, затенение является поведением по умолчанию, и Visual Studio предупреждает вас, когда затенение происходит без явного написания модификатора тени.

Примечание. Для меня я никогда не использовал ссылку базового класса на дочерний объект. Для таких случаев я всегда использую интерфейсы.

Я нашел другое отличие. Видеть это:

Sub Main()
    Dim X As New Derived
    Dim Y As Base = New Derived
    Console.WriteLine("X:" & X.Test())
    Console.WriteLine("Y:" & Y.Test())
    Console.WriteLine("X:" & CType(X, Base).Test)
    Console.WriteLine("X:" & X.Func())
    Console.WriteLine("Y:" & Y.Func())
    Console.WriteLine("X:" & CType(X, Base).Func)
    Console.ReadKey()
End Sub
Public Class Base
    Public Overridable Function Func() As String
        Return "Standard"
    End Function
    Function Test() As String
        Return Me.Func()
    End Function
End Class
Public Class Derived
    Inherits Base
    Public $$$ Function Func() As String
        Return "Passed By Class1" & " - " & MyBase.Func
    End Function
End Class

Если вы используете переопределения (где есть $$$), НЕТ никакого способа использовать Func для базового класса, либо если определение экземпляра является производным, и если определение является базовым, но экземпляр имеет тип производного.

Если вы используете Shadows, единственный способ увидеть Func в производном классе - это определить экземпляр как Derived, не передавая метод базового класса (X.Test возвращает Standard). Я думаю, что это главное: если я использую Shadows, метод не будет перегружать базовый метод внутри базовых методов.

Это ООП подход перегрузок. Если я получаю класс и ни в коем случае не хочу вызывать метод, я должен использовать перегрузки. Для экземпляров моих объектов нет способа вернуть "Стандарт" (думаю, кроме использования отражений). Я думаю, что intellisense сделать немного путаницы. Если я выделю Y.Func, то будет выделен Func в базовый класс, но будет выполнен Func в производный класс.

С Shadows новый метод доступен только напрямую. Такие как перегрузки, но скрывают перегрузки базового класса (я думаю, что это ошибка, возвращаемая до компиляции, потому что вы можете вызывать ее с использованием приведения, как, например, неявное выполнение с использованием перегрузки).

Тени могут быть очень полезны, если вы пишете обертку вокруг существующего элемента управления.

Например, вокруг выпадающего списка. Слежение AutoCompleteSource вы можете запретить ему устанавливать недопустимое значение для вашего специального вида комбобокса, даже если он приведен к обычному комбобоксу. Или сделайте предварительную обработку перед использованием mybase.AutoCompleteSource = value в свойстве слежки.

Тень позволяет вам делать определенные вещи, которые нельзя сделать с переопределениями.

В моем собственном случае: у меня есть несколько классов таблиц с общей функциональностью; но для которых сами коллекции бывают разных типов.

Public Class GenericTable
Protected Friend Overridable Property Contents As System.Collections.Generic.List(Of GenericItem)
    ... do stuff ...
End Class

Тогда у меня есть конкретные моменты:

Public Class WidgetTable
Inherits GenericTable
Protected Friend Shadows Property Contents As System.Collections.Generic.List(Of Widget)
    ... stuff is inhereted ...
End Class

Я не мог переопределить, потому что тип изменен.

VB использует более продвинутые концепции ООП, чем C#, для использования переопределения в производном классе в C# вы ДОЛЖНЫ пометить метод как "vitrual", Shadow позволяет вам делать перегрузку без этого.

Прочтите документацию MS об этом https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/declared-elements/differences-between-shadowing-and-overriding

Использование Теней редко, но верно. Кроме того, вы не можете переопределить общий (статический) метод. Таким образом, вы должны скрывать разделяемый метод, если хотите "переопределить" его.

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

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

Я хотел использовать System.Web.HttpContext.Current.Response вместо Response.redirectи нужно было удобство для кодирования как Response.redirect, Я определил свойство только для чтения с именем Response для тени оригинала в базовом классе. Я не мог использовать переопределения, так как это свойство не может быть переопределено. Очень удобно:)

Другие вопросы по тегам