c#中虚拟函数的实际用法是什么?
答案 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()
的许多派生类:Animal
,FlyingAnimal
,SwimmingAnimal
,BigAnimal
等在某一点上,你想要定义一个包含许多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();
....
}
}