总的来说,C# 中的每个抽象函数都是虚函数吗?

24

我在阅读 Stack Overflow 的问题什么是抽象函数和虚函数之间的区别?,并且想知道在 C# 或者一般情况下是否应该认为每个抽象函数都是虚函数。

对于“必须重载 / 可以重载”这个问题的回答让我有点疑惑。作为一个不懂 C# 的程序员,我倾向于认为抽象函数只是一个编译时的概念,而根据定义,每个抽象函数都应该是虚函数,因为你必须提供至少一个实现,并且还可以在继承体系中提供多个实现。

虚函数也有编译时的维度,因为你不能重写非虚函数,但它们主要是一个运行时的概念,因为它仅仅是基于实际接收者选择正确方法实现。

5个回答

49

是的。来自C# 3.0规范的第10.6.6节:

如果一个实例方法声明包含抽象修饰符,那么该方法被称为抽象方法。尽管抽象方法隐式地也是虚方法,但它不能有虚拟修饰符。


Note: - "implicitly also a virtual method" 翻译为“隐式地也是虚方法”,意思是抽象方法默认是虚方法,不需要再加virtual关键字。 - "modifier" 翻译为“修饰符” 或 “关键字”,根据上下文确定。

2
你无法得到比查看 C# 规范更好的答案。 :) - Quibblesome

4

必须使用虚拟方法 (而Jon Skeet已经推出了规范,证明它确实如此),因为在给定抽象基类的引用时,必须调用具体派生类的实现。例如,给定经典的Animal层次结构:

abstract class Animal{
    public abstract void Speak();
}

class Cat : Animal{
    public override void Speak(){Console.WriteLine("meow");}
}

class Dog : Animal{
    public override void Speak(){Console.WriteLine("bark");}
}

一个函数如果不是虚函数,则不能确定调用哪个Animal 对象的 Speak 方法。
static void TalkToAnimal(Animal a){
    Console.WriteLine("Hello, animal.");
    a.Speak();
}

请注意,接口实现默认情况下是虚拟的。由于接口与类的工作方式不同,为了找到接口方法的实现,真正的多态性并不是必需的。

抽象方法不需要是虚拟的,它们可以提供另一个关键字,在具体类中实现它,但不允许子类进一步重写它。 - Matthew Scharley
1
@Matthew Scharley:是的,它们需要是虚拟的。具体的派生类可以封闭其实现,防止进一步派生的覆盖,但这并不使函数变得不那么虚拟。调用仍然通过虚函数表进行,通过基类进行的调用仍然会调用派生类的函数。 - P Daddy

2
是的,它就是。以下是证明:

abstract class A {
    public abstract void Foo();
}
class B : A {
    public override void Foo()
    { /* must do */ }
}
class C : B {
    public override void Foo()
    { /* can do */ }
}

1

是的。

抽象属性声明指定属性的访问器是虚拟的,但不提供访问器的实际实现。(MSDN)


0

我认为你从"C++"的角度来看待这个问题(简洁明了,避免必要的关键词,节省击键)。

C#的哲学是代码的意图应该从阅读源代码中清晰明了,并且编译器应该尽可能地验证这个意图。

因此,虽然在生成的MSIL中抽象方法和虚方法之间几乎没有什么区别(或者说,在抽象类和非抽象类之间;或者在out参数和ref参数之间),但额外的关键词确实告诉维护程序员一些信息,并且允许编译器双重检查你所做的事情。


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