【发布时间】:2019-07-26 03:23:08
【问题描述】:
在基类型“virtual”中声明方法,然后在子类型中使用“override”关键字覆盖它与在声明子类型中的匹配方法?
【问题讨论】:
-
MSDN say "使用
new创建一个同名的新成员并导致原来的成员被隐藏,而override扩展了继承成员的实现"
标签: c# syntax overriding method-hiding member-hiding
在基类型“virtual”中声明方法,然后在子类型中使用“override”关键字覆盖它与在声明子类型中的匹配方法?
【问题讨论】:
new 创建一个同名的新成员并导致原来的成员被隐藏,而override 扩展了继承成员的实现"
标签: c# syntax overriding method-hiding member-hiding
“new”关键字不会覆盖,它表示一个与基类方法无关的新方法。
public class Foo
{
public bool DoSomething() { return false; }
}
public class Bar : Foo
{
public new bool DoSomething() { return true; }
}
public class Test
{
public static void Main ()
{
Foo test = new Bar ();
Console.WriteLine (test.DoSomething ());
}
}
这将打印 false,如果您使用覆盖,它将打印 true。
(取自 Joseph Daigle 的基本代码)
所以,如果你在做真正的多态,你应该总是覆盖。唯一需要使用“new”的地方是方法与基类版本没有任何关系。
【讨论】:
virtual 并在派生中具有override 完全相同?它为什么存在?即使没有new,代码仍然可以运行 - 所以它纯粹只是可读性吗?
new 关键字实际上创建了一个仅存在于该特定类型上的全新成员。
例如
public class Foo
{
public bool DoSomething() { return false; }
}
public class Bar : Foo
{
public new bool DoSomething() { return true; }
}
该方法存在于两种类型上。当您使用反射并获取类型为Bar 的成员时,您实际上会发现两个名为DoSomething() 的方法看起来完全一样。通过使用new,您可以有效地将实现隐藏在基类中,这样当类派生自Bar(在我的示例中)时,对base.DoSomething() 的方法调用会转到Bar 而不是Foo。
【讨论】:
这里有一些代码来理解虚拟和非虚拟方法的行为差异:
class A
{
public void foo()
{
Console.WriteLine("A::foo()");
}
public virtual void bar()
{
Console.WriteLine("A::bar()");
}
}
class B : A
{
public new void foo()
{
Console.WriteLine("B::foo()");
}
public override void bar()
{
Console.WriteLine("B::bar()");
}
}
class Program
{
static int Main(string[] args)
{
B b = new B();
A a = b;
a.foo(); // Prints A::foo
b.foo(); // Prints B::foo
a.bar(); // Prints B::bar
b.bar(); // Prints B::bar
return 0;
}
}
【讨论】:
new 来“隐藏”基本方法,而根本不使用 override 似乎也能做到这一点?
virtual 的方法,如果编译器在没有 virtual 签名的类“血统”上看到相同的函数名称,编译器会抱怨
new 基本上允许父方法保持可访问性(当孩子被强制转换/用作父类型时)。相反,virtual+override 保证子方法被使用,而不管子如何被强制转换/使用(当然只有在覆盖时)。
除了技术细节之外,我认为使用虚拟/覆盖可以在设计上传达大量语义信息。当您声明一个虚拟方法时,您表明您希望实现类可能希望提供它们自己的非默认实现。同样,在基类中省略它,声明了默认方法应该足以满足所有实现类的期望。类似地,可以使用抽象声明来强制实现类提供它们自己的实现。同样,我认为这传达了很多关于程序员期望如何使用代码的信息。如果我同时编写基类和实现类并且发现自己使用 new 我会认真重新考虑不在父类中使方法为虚拟的决定并明确声明我的意图。
【讨论】:
virtual / override 告诉编译器这两个方法是相关的,并且在某些情况下,当您认为您正在调用第一个(虚拟)方法时,调用第二个(覆盖)实际上是正确的代替方法。这是多态的基础。
(new SubClass() as BaseClass).VirtualFoo()
将调用子类的重写 VirtualFoo() 方法。
new 告诉编译器您正在向派生类添加一个与基类中的方法同名的方法,但它们彼此没有关系。
(new SubClass() as BaseClass).NewBar()
将调用 BaseClass 的 NewBar() 方法,而:
(new SubClass()).NewBar()
将调用子类的 NewBar() 方法。
【讨论】:
我总是觉得用图片更容易理解这样的事情:
再次获取 joseph daigle 的代码,
public class Foo
{
public /*virtual*/ bool DoSomething() { return false; }
}
public class Bar : Foo
{
public /*override or new*/ bool DoSomething() { return true; }
}
如果你再这样调用代码:
Foo a = new Bar();
a.DoSomething();
注意:重要的是我们的对象实际上是一个Bar,但我们将它存储在Foo类型的变量中(这类似于强制转换它)
那么结果会如下,具体取决于你在声明类时是使用virtual/override还是new。
【讨论】:
new 关键字用于隐藏。 - 表示您在运行时隐藏您的方法。输出将基于基类方法。override 用于覆盖。 - 表示您正在使用基类的引用调用派生类方法。输出将基于派生类方法。【讨论】:
我的解释来自于使用 properties 来帮助理解差异。
override 很简单,对吧?基础类型覆盖父类型。
new 可能是误导(对我来说是这样)。使用属性更容易理解:
public class Foo
{
public bool GetSomething => false;
}
public class Bar : Foo
{
public new bool GetSomething => true;
}
public static void Main(string[] args)
{
Foo foo = new Bar();
Console.WriteLine(foo.GetSomething);
Bar bar = new Bar();
Console.WriteLine(bar.GetSomething);
}
使用调试器,您会注意到 Foo foo 有 2 GetSomething 属性,因为它实际上有两个版本的属性,Foo 和 Bar,以及要知道使用哪一个,c#“选择”当前类型的属性。
如果您想使用 Bar 的版本,您应该使用 override 或改用 Foo foo。
Bar bar 只有 1,因为它想要 GetSomething 的完全新行为。
【讨论】:
不使用任何东西标记方法意味着:使用对象的编译类型绑定此方法,而不是运行时类型(静态绑定)。
用virtual 标记方法意味着:使用对象的运行时类型绑定此方法,而不是编译时类型(动态绑定)。
在派生类中用override 标记基类virtual 方法意味着:这是使用对象的运行时类型绑定的方法(动态绑定)。
在派生类中用new标记基类virtual方法意味着:这是一个新方法,与基类中的同名方法没有关系,应该使用对象的编译时间绑定类型(静态绑定)。
派生类中没有标记基类virtual方法意味着:这个方法被标记为new(静态绑定)。
标记一个方法abstract的意思是:这个方法是虚拟的,但我不会为它声明一个主体,它的类也是抽象的(动态绑定)。
【讨论】:
using System;
using System.Text;
namespace OverrideAndNew
{
class Program
{
static void Main(string[] args)
{
BaseClass bc = new BaseClass();
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();
// The following two calls do what you would expect. They call
// the methods that are defined in BaseClass.
bc.Method1();
bc.Method2();
// Output:
// Base - Method1
// Base - Method2
// The following two calls do what you would expect. They call
// the methods that are defined in DerivedClass.
dc.Method1();
dc.Method2();
// Output:
// Derived - Method1
// Derived - Method2
// The following two calls produce different results, depending
// on whether override (Method1) or new (Method2) is used.
bcdc.Method1();
bcdc.Method2();
// Output:
// Derived - Method1
// Base - Method2
}
}
class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Base - Method1");
}
public virtual void Method2()
{
Console.WriteLine("Base - Method2");
}
}
class DerivedClass : BaseClass
{
public override void Method1()
{
Console.WriteLine("Derived - Method1");
}
public new void Method2()
{
Console.WriteLine("Derived - Method2");
}
}
}
【讨论】: