"Тени" и "Переопределения" в VB.NET
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
для тени оригинала в базовом классе. Я не мог использовать переопределения, так как это свойство не может быть переопределено. Очень удобно:)