c#中虚函数的实际用法

时间:2009-06-30 06:54:36

标签: c# virtual-functions

c#中虚拟函数的实际用法是什么?

10 个答案:

答案 0 :(得分:80)

所以基本上如果在你的祖先类中你想要一个方法的特定行为。如果您的后代使用相同的方法但具有不同的实现方式,则可以覆盖,如果它具有虚拟关键字。

using System;
class TestClass 
{
   public class Dimensions 
   {
      public const double pi = Math.PI;
      protected double x, y;
      public Dimensions() 
      {
      }
      public Dimensions (double x, double y) 
      {
         this.x = x;
         this.y = y;
      }

      public virtual double Area() 
      {
         return x*y;
      }
   }

   public class Circle: Dimensions 
   {
      public Circle(double r): base(r, 0) 
      {
      }

      public override double Area() 
      { 
         return pi * x * x; 
      }
   }

   class Sphere: Dimensions 
   {
      public Sphere(double r): base(r, 0) 
      {
      }

      public override double Area()
      {
         return 4 * pi * x * x; 
      }
   }

   class Cylinder: Dimensions 
   {
      public Cylinder(double r, double h): base(r, h) 
      {
      }

      public override double Area() 
      {
         return 2*pi*x*x + 2*pi*x*y; 
      }
   }

   public static void Main()  
   {
      double r = 3.0, h = 5.0;
      Dimensions c = new Circle(r);
      Dimensions s = new Sphere(r);
      Dimensions l = new Cylinder(r, h);
      // Display results:
      Console.WriteLine("Area of Circle   = {0:F2}", c.Area());
      Console.WriteLine("Area of Sphere   = {0:F2}", s.Area());
      Console.WriteLine("Area of Cylinder = {0:F2}", l.Area());
   }
}

编辑:评论中的问题 如果我不在基类中使用虚拟关键字,它会起作用吗?

如果您在后代课程中使用override关键字,则无效。您将生成编译器错误CS0506'function1':无法覆盖继承的成员'function2',因为它未标记为“virtual”,“abstract”或“override”

如果你不使用覆盖,你将得到CS0108警告'desc.Method()'隐藏继承的成员'base.Method()'如果想要隐藏,请使用new关键字。

要解决此问题,请将new关键字置于隐藏的方法前面。

e.g。

  new public double Area() 
  {
     return 2*pi*x*x + 2*pi*x*y; 
  }

..并且必须覆盖派生类中的虚方法吗?
不,如果你不重写方法,后代类将使用它继承的方法。

答案 1 :(得分:64)

理解虚函数的实际用法的关键是要记住,某个类的对象可以被赋予从第一个对象的类派生的类的另一个对象。

例如:

class Animal {
   public void eat() {...}
}

class FlyingAnimal : Animal {
   public void eat() {...}
}

Animal a = new FlyingAnimal();

Animal类的函数eat()通常描述动物应该如何进食(例如将物体放入口中并吞咽)。

但是,FlyingAnimal类应该定义一种新的eat()方法,因为飞行动物有一种特殊的饮食方式。

因此,我想到的问题是:在我声明类型a的变量Animal并将其归为FlyingAnimal类型的新对象之后,{{1}将会是什么}做什么?这两种方法中的哪一种被称为?

这里的答案是:因为a.eat()的类型为a,所以它会调用Animal的方法。编译器很笨,并且不知道你要将另一个类的对象分配给Animal变量。

这里是a关键字的作用:如果你将方法声明为virtual,你基本上是告诉编译器“要小心我在做一些你无法处理的聪明的东西因为你不那么聪明“。因此编译器不会尝试将调用virtual void eat() {...}链接到两种方法中的任何一种,而是告诉系统在运行时执行>

因此,只有在代码执行时,系统才会查看a.eat()内容类型,而不是其声明的类型,并执行a的方法。

你可能想知道:为什么我要这么做呢?为什么不从一开始就说FlyingAnimal

原因在于,例如,您可能拥有来自FlyingAnimal a = new FlyingAnimal()的许多派生类:AnimalFlyingAnimalSwimmingAnimalBigAnimal等在某一点上,你想要定义一个包含许多WhiteDog s的世界,所以你说:

Animal

我们拥有100个快乐动物的世界。 你可以在某个时候初始化它们:

Animal[] happy_friends = new Animal[100];

在一天结束时,你希望每个人在睡觉前都吃饭。所以你想说:

...
happy_friends[2] = new AngryFish();
...
happy_friends[10] = new LoudSnake();
...

你可以看到,每只动物都有自己的进食方法。只有使用虚拟功能才能实现此功能。否则,每个人都会被迫以完全相同的方式“吃掉”:如for (int i=0; i<100; i++) { happy_friends[i].eat(); } 类中最常见的eat函数所述。

编辑: 在Java等常见的高级语言中,这种行为实际上是默认

答案 2 :(得分:7)

像任何其他语言一样..当你想要多态时。这有很多用处。例如,您想要抽象从控制台或文件或其他设备读取输入的方式。您可以使用通用阅读器界面,然后使用虚函数进行多个具体实现。

答案 3 :(得分:4)

e.g。代理方法。即在运行时覆盖方法。例如,NHibernate使用它来支持延迟加载。

答案 4 :(得分:3)

基本上,虚拟成员允许您表达多态,派生类可以使用与其基类中的方法具有相同签名的方法,并且基类将调用派生类的方法。

一个基本的例子:

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

答案 5 :(得分:3)

这允许实现后期绑定,这意味着在运行时而不是在编译时确定将调用哪个对象的成员。请参阅Wikipedia

答案 6 :(得分:1)

来自here

  

在面向对象的编程中,a   虚函数或虚方法是   一种行为的函数或方法   可以在继承中重写   通过具有相同功能的类   签名。

答案 7 :(得分:1)

例如,您有一个基类Params和一组派生类。您希望能够在存储从params派生的所有可能类的数组上执行相同的操作。

没问题 - 声明方法virtual,向Params类添加一些基本实现,并在派生类中重写它。现在你可以遍历数组并通过引用调用方法 - 将调用正确的方法。

class Params {
public:
   virtual void Manipulate() { //basic impl here }
}

class DerivedParams1 : public Params {
public:
   override void Manipulate() {
      base.Manipulate();
      // other statements here
   }
};

// more derived classes can do the same

void ManipulateAll( Params[] params )
{
    for( int i = 0; i < params.Length; i++ ) {
       params[i].Manipulate();
    }
 }

答案 8 :(得分:1)

在c#

中使用虚拟功能

虚函数主要用于覆盖具有相同签名的派生类中的基类方法。

当派生类继承基类时,派生类的对象是对派生类或基类的引用。

虚函数由编译器解决(即运行时绑定)

基类中的

virtual,根据引用的对象的实际类型调用函数的派生类最多的实现,而不管指针或引用的声明类型如何。如果它不是virtual,则解析方法early并根据声明的指针或引用类型选择调用的函数。

答案 9 :(得分:0)

示例

让我们考虑 System.Object 中的 ToString()方法。因为此方法是System.Object的成员,所以它在所有类中都是继承的,并将为所有这些类提供ToString()方法。

namespace VirtualMembersArticle
{
    public class Company
    {
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Company company = new Company() { Name = "Microsoft" };
            Console.WriteLine($"{company.ToString()}");
            Console.ReadLine();
        }   
    }
}

上一个代码的输出是:

VirtualMembersArticle.Company

让我们考虑我们要更改从Company类中的System.Object继承的ToString()方法的标准行为。要实现此目标,只需使用override关键字声明该方法的另一种实现即可。

public class Company
{
    ...
    public override string ToString()
    {
        return $"Name: {this.Name}";
    }         
}

现在,当调用虚拟方法时,运行时将检查其派生类中的重写成员,如果存在则调用它。我们的应用程序的输出将是:

Name: Microsoft

实际上,如果检查System.Object类,您会发现该方法被标记为虚方法。

namespace System
{
    [NullableContextAttribute(2)]
    public class Object
    {
        ....
        public virtual string? ToString();
        ....
    }
}