17 回答
Overrides是更正常的限定符。如果子类以这种方式重新定义基类函数,那么无论如何引用子对象(使用基类或子类引用),调用的都是子函数。
另一方面,如果子类函数隐藏基类函数,则通过基类引用访问的子对象将使用该基类函数,尽管它是子对象。
仅当使用匹配的子引用访问子对象时才使用子函数定义。
阴影可能不会像您认为的那样做。
考虑以下类:
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 定义,这样如果 A 类的 Add 方法被修改,则不会影响 B 的 add。(隐藏所有基类“Add”方法。将无法从 B 的实例调用 A.Add(x, y, z)。)
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
遮蔽的一个例子:假设您想在第三方组件中使用一个函数,但该函数是受保护的。您可以通过简单的继承绕过此约束并公开一个基本上调用其基函数的阴影函数:
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
“shadows”关键字本质上是说“如果访问此对象的人知道它属于这种类型或其后代之一,则使用此成员;否则使用基成员。” 最简单的例子可能是基类 ThingFactory,它包括一个返回 Thing 的“MakeNew”方法和一个派生自 ThingFactory 的类 CarFactory,其“MakeNew”方法总是返回一个派生类型 Car 的 Thing。如果例程知道它持有的 ThingFactory 恰好是 CarFactory,那么它将使用影子 CarFactory.MakeNew(如果存在),它可以将返回类型指定为 Car。如果一个例程不知道它的 ThingFactory 实际上是一个 CarFactory,
顺便说一句,阴影的另一个很好的用途是防止派生类访问将不再起作用的受保护方法。除了分配一个新成员之外,没有办法简单地从派生类中隐藏一个成员,但是可以通过声明一个具有该名称的新受保护空类来防止派生类对受保护成员执行任何操作。例如,如果在一个对象上调用 MemberwiseClone 会破坏它,可以声明:
受保护的阴影类 MemberwiseClone 结束类请注意,这并不违反诸如 Liskov 替换原则之类的 OOP 原则,因为这仅适用于可能使用派生类代替基类对象的情况。如果 Foo 和 Bar 继承自 Boz,则接受 Boz 参数的方法可以合法地传递到 Foo 或 Bar 中。另一方面,Foo 类型的对象会知道它的基类对象是 Boz 类型。它永远不会是其他任何东西(例如,它保证不是酒吧)。
我认为人们在这里确实有两种情况,两者都是合法的。您真的可以将它们分解为基类设计者和多年后实现无法修改基类的子类的开发人员。所以,是的,如果你有这种奢侈,最好的办法就是超越。这是干净的OOD方法。
另一方面,您可能有类似上面给出的示例,在该等式的另一端,您必须实现一个子类,并且您无法更改您需要覆盖的方法未标记为可覆盖的事实。举个例子
Public Shadows Function Focus() As Boolean
txtSearch.Focus()
Return MyBase.Focus()
End Function
在这种情况下,我从 Winform 控件类继承了我的类,不幸的是它没有被标记为可覆盖。在这一点上,我面临着只是让代码“纯粹”或让它更容易理解。此控件的客户端只是想调用 control.Focus() 并且可能不在乎。我可以将这个方法命名为 FocusSearchText() 或 Focus2 等,但我相信上面的方法对于客户端代码来说要简单得多。确实,如果客户端随后将此控件转换为基类并调用 Focus,我的代码将不会执行。但那是相当遥远的。
最后,它归结为一个判断电话,一个你必须做出的决定。
这是最近的 MSDN 链接: Differences between shadowing and overriding
阴影可防止后续基类修改引入您已在派生类中定义的成员。通常在以下情况下使用阴影:
** 您预计您的基类可能会被修改以使用与您的名称相同的名称来定义一个元素。*
** 您希望自由更改元素类型或调用顺序。*
(我还没有调查范围和类型的使用情况)
那么这里是代码的答案。
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 会在您进行阴影时发出警告,而无需您显式编写阴影修饰符。
注意:对我来说,我从未使用过对子对象的基类引用。对于这种情况,我总是使用接口。
Shadow 允许您执行某些无法通过覆盖完成的事情。
在我自己的情况下:我有几个具有通用功能的表类;但是对于他们来说,藏品本身是不同的类型。
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
我无法覆盖,因为类型已更改。
我发现了另一个不同之处。看到这个:
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
如果您使用 Overrides(其中有 $$$),如果实例的定义是 Derived,并且如果定义是基类但实例是 Derived 类型,则无法在 Base 类上使用 Func。
如果您使用 Shadows,您可以看到 Func 进入派生类的唯一方法是将实例定义为 Derived,并且不传递给基类的方法(X.Test 返回标准)。我认为这是主要的:如果我使用阴影,该方法不会在基方法中重载基方法。
这就是 Overloads 的 OOP 方法。如果我派生一个类并且在任何情况下我都不希望调用一个方法,我必须使用重载。对于我的对象的实例,没有办法返回“标准”(我认为除了使用反射)。我认为智能感知会造成一些混乱。如果我突出显示 Y.Func,则会将 Func 突出显示为基类,但将 Func 执行为派生类。
使用 Shadows,新方法只能直接访问。比如Overloads,但是隐藏了基类的重载(我认为是编译前返回的错误,因为可以使用强制转换调用它,比如使用重载隐式完成)。
如果您正在围绕现有控件编写包装器,那么阴影可能非常有用。
例如在组合框周围。通过遮蔽AutoCompleteSource
你可以防止它被设置为你的特殊类型的组合框的非法值,即使它被投射到一个普通的组合框。mybase.AutoCompleteSource = value
或者在使用阴影属性之前进行一些预处理。
阴影的使用很少见,但确实如此。此外,您不能覆盖共享(静态)方法。因此,如果您想“覆盖”它,您必须隐藏共享方法。
VB 使用比 C# 更高级的 OOP 概念,要在 C# 的派生类中使用覆盖,您必须将方法标记为“虚拟”,Shadow 允许您在没有这个的情况下进行重载。
我同意吉姆的观点。我也从未找到过 Shadows 的合法用途。通常,如果我看到它,我认为代码的子部分需要重构一下。
我想它在那里是为了让您可以从您无法控制源代码的程序集中隐藏一个方法。在这种情况下,重构父类是不可能的。
我想使用System.Web.HttpContext.Current.Response
而不是Response.redirect
,并且需要方便地编写为Response.redirect
. 我定义了一个只读属性,命名Response
为隐藏基类中的原始属性。我不能使用覆盖,因为这个属性是不可覆盖的。很方便:)
我不认为Shadows 真的是一个OOP 概念。Overrides 表示您正在为祖先类中声明的方法/属性等提供新的或附加的功能。阴影确实欺骗编译器认为父方法/属性等甚至不存在。
我对影子没有用处。坚持覆盖。VB 多年来提供的这些有用的小“功能”总是在某些时候让您感到悲伤。