为什么我们要使用虚拟函数和重载?

17

如果我们不使用override和virtual会得到相同的效果,那么为什么我们要使用它们?

示例1:

class BaseClass
{
    public virtual string call()
    {
        return "A";
    }
}

class DerivedClass : BaseClass
{
    public override string call()
    {
        return "B";
    }
}

输出:B

例子2:

class BaseClass
{
    public string call()
    {
        return "A";
    }
}

class DerivedClass : BaseClass
{
    public string call()
    {
        return "B";
    }
}

输出结果仍然相同:

输出:B

运行测试:

class Program
{
    static void Main(string[] args)
    {
        DerivedClass dc = new DerivedClass();
        Console.WriteLine(dc.call());
        Console.ReadKey();
    }
}

编译器是否会在编译时自动添加virtual和override关键字?

如果有人能解释一下使用virtual和override的原因,我会很高兴的。


1
如果在调用add函数之前将派生类derivedClass转换为基类,您将得到不同的结果:1 => 2和2 => 1。 - Peter
@CD.. 没有重复,这是关于“覆盖和虚拟”的问题。 - Racooon
2
最好发布编译后的示例,这样会更好。 - Marc Gravell
@VuralAcar:虽然问题略有不同,但原理相同。你只是误解了多态和方法隐藏之间的区别。 - Roman
@MarcGravell 我刚刚编辑了。 - Racooon
3
你的测试显然没有展示出差异。把代码行改为“BaseClass dc = new DerivedClass();”并再次尝试。现在有区别吗? - Eric Lippert
4个回答

22
(注意,我悄悄地忽略了编译错误。)
现在执行:
BaseClass obj = new DerivedClass();
Console.WriteLine(obj.call());

如果没有使用virtual,那么这将会输出A,但实际上应该输出一个DerivedClass所写的B。这是因为它简单地调用了BaseClass的实现(由于obj被声明为BaseClass类型,并且没有定义多态性)。


3
请仔细观察我的场景与你的场景之间的区别,现在将你的代码更改为 BaseClass bc = new DerivedClass(); Console.WriteLine(bc.call());。强调一点:编译时它并不会 自动添加这个东西;只是你正在测试不同的东西。 - Marc Gravell
5
@Vural 因为那是有关重要的时刻。如果你不使用多态性,则它们会显示相同的内容。不同之处在于,virtual支持抽象和替换。你问“为什么要使用{这些}” - 我的回答是:支持这种情景。很多时候,我们不知道具体/最终类型 - 我们只知道基础类型。例如,在WinForms中的Control或在ASP.NET中的Page - Marc Gravell
@Marc Gravell,您能否解释一下在现实世界中我们何时需要使用方法重写的场景? - user5093161
1
@TAHASULTANTEMURI 这就是多态的核心。基本示例:您有一个类并希望更改文本表示(覆盖 ToString())或相等性如何工作(覆盖 Equals()GetHashCode())。更复杂的示例:您正在实现自定义 ADO.NET 提供程序,因此您会子类化 DbConnectionDbParameterDbDataReader 等,并覆盖各种方法以添加您的实现。 - Marc Gravell
非常感谢您,先生! - user5093161
显示剩余3条评论

5

虚拟和覆盖是面向对象编程中继承的基本机制。 当你在像C#或Java这样的语言中使用类时,理解这一点可能是最重要的。

http://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)

继承允许您重用代码,添加新字段、属性和方法,或替换先前定义的类的方法和属性。

虚拟和覆盖允许您替换方法的内容,当我说替换时,我是指完全替换。

我会给您提供一个很好的例子。

public class MyClassEnglish
{
    public virtual string SomethingToSay()
    {
        return "Hello!";
    }

    public void WriteToConsole()
    {
        Console.WriteLine(this.SomethingToSay());
    }
}

public class MyClassItalian :
    MyClassEnglish
{
    public override string SomethingToSay()
    {
        return "Ciao!";
    }
}

int main()
{
    MyClassItalian it = new MyClassItalian();

    it.WriteToConsole();
}

如果您省略virtual和override,MyClassItalian将打印“Hello!”而不是“Ciao!”。在您的示例中,您展示了一种Shadowing技术,但编译器应该会给出警告。如果您想隐藏基类中的方法,则应添加“new”关键字。隐藏方法并不等同于重写!只是隐藏。我能想到的一个可能的用途是,当您需要某种优化时可以使用它。
public abstract class MySpecialListBase
{
    public int Count()
    {
        return this.GetCount();
    }

    protected abstract int GetCount();
}

public sealed class MySpecialArrayList : MySpecialListBase
{
    int count;

    public new int Count()
    {
        return this.count;
    }

    protected override int GetCount()
    {
        return this.count;
    }
}

现在,您可以在所有代码中使用MySpecialListBase,在调用Count()时它将调用虚方法GetCount()。 但是,如果您只使用MySpecialArrayList,它将调用优化的Count(),该方法不是虚拟的,仅返回一个字段,从而提高性能。
// This works with all kind of lists, but since it is a more general purpose method it will call the virtual method.    
public void MyMethod(MySpecialListBase list)
{
    Console.WriteLine(list.Count());
}

// This works only with MySpecialArrayList, and will use the optimized method.
public void MyMethod(MySpecialArrayList list)
{
    Console.WriteLine(list.Count());
}

0

我能想到的最好的例子是当你创建自己的对象(类)并且你必须将该对象的列表添加到组合框中时,这非常有用。

当你向组合框添加对象时,你希望能够控制每个项目显示的文本。Object.toString是一个虚拟方法。 http://msdn.microsoft.com/en-us/library/system.object.tostring.aspx 因此,你可以重写该方法并设置.toString以通过覆盖它正确地显示关于你的对象的信息。

public MyClass()
{
     private int ID;
     public override string ToString()
     {
         return "My Item:" + ID;
     }
}

0

方法重写:

在父类中定义或实现一个虚拟方法,然后在子类中替换它。

当你决定将一个方法声明为虚拟方法时,你允许派生类通过自己的实现来扩展和重写该方法。你还可以让扩展的方法调用父类方法的代码。

在大多数面向对象的语言中,你也可以选择隐藏父类方法。当你引入一个具有相同名称和签名的新实现而不是重写的方法时,你就隐藏了父类方法。

C#中的重写 在C#中,你可以使用virtual关键字在父类中指定一个虚拟方法,并使用override关键字在子类中扩展(或替换)它。

在子类方法中使用base关键字执行父类方法中的代码,例如base.SomeMethod()。

语法示例:

class Robot
{
  public virtual void Speak()
  {
  }
}

class Cyborg:Robot
{
   public override void Speak()
   {
   }
}

覆盖细节 您无法覆盖常规的非虚方法或静态方法。 父方法的第一个版本必须是虚拟的或抽象的。 您可以覆盖任何标记为virtual、abstract或override(已经被覆盖)的父方法。 这些方法必须具有相同的签名。 这些方法必须具有相同的可见性(相同的访问级别)。 使用base关键字来引用父类,如base.SomeMethod()。 C# 覆盖示例 以下代码片段演示了在后代类中使用virtual和override来覆盖父方法。

using System;

class Dog
{
   public virtual void Bark()
   {
       Console.WriteLine("RUFF!");
   }
}

class GermanShepard:Dog
{
    public override void Bark()
    {
       Console.WriteLine("Rrrrooouuff!!");
    }
}

class Chiuaua:Dog
{
    public override void Bark()
    {
         Console.WriteLine("ruff");
    }
}
class InclusionExample
{
     public static void Main()
     {
        Dog MyDog=new Dog();    
        MyDog=new GermanShepard();
        MyDog.Bark(); // prints Rrrrooouuff!!

        MyDog=new Chiuaua();
        MyDog.Bark(); // prints ruff;
    }
}

隐藏方法使用New关键字
使用new关键字引入父方法的新实现(这将隐藏父方法)。您可以不使用new来隐藏方法,但是会收到编译器警告。使用new将抑制警告。
new和override修饰符具有不同的含义。new修饰符创建一个具有相同名称、签名和可见性的新成员,并隐藏原始成员。override修饰符扩展了继承成员的实现,并允许您实现基于继承的多态性。
避免引入新成员:有时候有明显的理由引入一个与父方法具有相同名称、签名和可见性的新方法。在这些明显的情况下,引入新成员是一个强大的功能。然而,如果您没有明确的理由,那么通过命名新方法为独特且适当的名称来避免引入方法的新版本。
class Robot : System.Object
{
  public void Speak()
  {
    MessageBox.Show("Robot says hi");
  }
}

class Cyborg : Robot
{
  new public void Speak()
  {
    MessageBox.Show("hi");
  }
}

调用基类版本 在面向对象编程中,一个常见的任务是通过首先执行父类方法代码,然后添加代码来扩展方法。使用base关键字来引用父类,如base.SomeMethod()。

class Robot : System.Object
{
    public virtual void Speak()
    {
       MessageBox.Show("Robot says hi");
    }
}
class Cyborg : Robot
{
  public override void Speak()
  {
      base.Speak(); 
      MessageBox.Show("hi");
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接