C#虚拟和重写关键字

7
我认为我已经搞定了这个问题,然而当我查看工作中的一些源代码时,我始终在想,为什么我从 MSDN 中读到的内容与我在源代码中看到的矛盾如此之多...

我的理解是,在方法声明中可以使用 virtual 关键字,以允许任何派生类对其进行覆盖。

然后,在实现超类的虚拟方法时,派生类中需要使用 override 关键字....

例如:

public abstract class A
{
    public virtual string GetName();
}


public class B:A
{

    //assume there are some defined properties.
    public override string GetName()
    {
        return FirstName;
    }
}

我有几个问题:
1) 如果一个方法没有实现,是否真的需要将其定义为虚拟方法? 不使用virtual和override,在子类中重写它不就行了吗?
2) 如果(1)是不正确的,那么我是否正确地认为每个虚拟方法都必须在使用它的子类中被重写。。。。
编辑:
你说得对,我的代码无法编译... 我想知道为什么....我理解了你们的答案,但是我看到了这个:
public abstract class RequestHandler<TRequest, TResponse> : RequestHandler, IRequestHandler<TRequest>, IRequestHandler, IDisposable, ITypedRequestHandler
    where TRequest : global::Agatha.Common.Request
    where TResponse : global::Agatha.Common.Response, new()
{
    protected RequestHandler();

    public virtual void AfterHandle(TRequest request);
    public virtual void BeforeHandle(TRequest request);
    public override Response CreateDefaultResponse();
    public TResponse CreateTypedResponse();
    public override Response Handle(Request request);
    public abstract Response Handle(TRequest request);
}

上述代码不会导致编译器报错...

你的示例不能编译(“A.GetName() 必须声明主体,因为它没有标记为 abstractexternpartial”)。 - dtb
你的A类编译时会提示缺少抽象吗? - V4Vendetta
当然,类A中的方法应该是公共抽象字符串GetName(); - Vedran
你对上述编辑中的源代码有什么想法?根据你的回答,上述源代码无效,但编译器并没有报错。 - user559142
我认为上述源代码是无效的。不幸的是,它似乎引用了我没有的东西(Agatha)。你能否将其缩小为一个更小的代码示例,不进行外部引用,但仍然表现出相同的行为? - Chris
7个回答

10
首先,上面的代码是无效的。虚方法仍然必须有一个带有默认实现的主体。要做你上面所做的事情,你需要使用abstract关键字而不是virtual。 abstract意味着没有提供方法主体,但从它派生的任何类都必须实现此方法(除非它也是抽象的)。
我认为这基本上回答了你的问题...
1.如果它没有实现,则不能是虚拟的,它必须是抽象的。如果它有一个什么也不做的实现,那么必须实现它。
2.虚拟类的整个重点在于它具有默认行为,因此您可以选择是否覆盖它。如果它是抽象的,那么您必须覆盖它(除非您正在派生另一个抽象类)。

是的,这正是我所想的,但我有一些源代码,在这里编译器不会抱怨...请参见上面的编辑。 - user559142
没错。如果实际上存在该函数体,使用 virtual 就不会强制要求继承类去实现该方法。 - Myrtle
谢谢,这很有道理,但我仍然对编辑中的上述源代码感到困惑。 - user559142

3
如果没有实现,定义一个方法是否真的需要将其设置为虚方法?您可以将该方法设置为抽象方法(这将隐式地将其设置为虚方法)。
如果您只是在子类中“覆盖”它而不显式重写它,则它将不再是相同的方法,并且在基类变量上调用该方法不会调用派生方法(它不会参与多态)。您只是“隐藏”了基类的方法(如果这确实是您想要做的,编译器实际上会警告您,您必须使用"new"修饰符)。以下示例将使其更加清晰:
class B
{
    public virtual void M() { Console.WriteLine("B.M") };
}

class D1 : Base
{
    // Hides the base method
    public new void M() { Console.WriteLine("D1.M") };
}


class D2 : Base
{
    // Overrides the base method
    public override void M() { Console.WriteLine("D2.M") };
}

...

D1 d1 = new D1();
d1.M(); // Prints "D1.M"
B b1 = d1;
b1.M(); // Prints "B.M", because D1.M doesn't override B.M

D2 d2 = new D1();
d2.M(); // Prints "D2.M"
B b2 = d2;
b2.M(); // Also prints "D2.M", because D2.M overrides B.M

如果(1)不正确,那么我认为每个使用它的子类都必须覆盖每个虚方法...不对,只有当它是抽象的时才需要这样做...虚方法可以有一个实现,在这种情况下,派生类不被强制覆盖它。

1

1)如果一个方法没有实现,真的有必要将其定义为虚方法吗?毫无疑问,它可以在子类中被重写而不使用虚和覆盖。

正如其他答案所说,虚方法需要有实现。您将其与抽象混淆了。

如果您想知道是否需要将具有实现的虚方法声明为虚方法:在C#中,是的,这是必要的。在Java中,您可以覆盖任何旧方法。这是C#的设计决策,要求使用虚关键字明确允许覆盖,以便方法不能被覆盖,除非程序员打算这样做。

如果程序员没有使用虚表达意图,您仍然可以使用new关键字“覆盖”方法。但是,这个过程有点不同。希望这段代码能帮助说明这个概念:

class Program
{
    static void Main(string[] args)
    {
        var baseC = new BaseClass();
        var extC = new ExtClass();
        var lazyC = new LazyClass();

        Console.WriteLine(baseC.NewMethod());
        Console.WriteLine(baseC.VirtualOverrideMethod());

        Console.WriteLine("---");

        Console.WriteLine(extC.NewMethod());
        Console.WriteLine(extC.VirtualOverrideMethod());

        Console.WriteLine("---");

        Console.WriteLine(((BaseClass) extC).NewMethod());
        Console.WriteLine(((BaseClass) extC).VirtualOverrideMethod()); // Redundant typecast

        Console.WriteLine("---");

        Console.WriteLine(lazyC.VirtualOverrideMethod());

        Console.ReadKey();
    }

    public class BaseClass
    {
        public BaseClass()
        {

        }

        public string NewMethod()
        {
            return "NewMethod of BaseClass";
        }

        public virtual string VirtualOverrideMethod()
        {
            return "VirtualOverrideMethod of BaseClass";
        }
    }

    class ExtClass : BaseClass
    {
        public new string NewMethod()
        {
            return "NewMethod of ExtClass";
        }

        public override string VirtualOverrideMethod()
        {
            return "VirtualOverrideMethod of ExtClass";
        }
    }

    class LazyClass : BaseClass
    {
    }
}

输出:

NewMethod of BaseClass
VirtualOverrideMethod of BaseClass
---
NewMethod of ExtClass
VirtualOverrideMethod of ExtClass
---
NewMethod of BaseClass
VirtualOverrideMethod of ExtClass
---
VirtualOverrideMethod of BaseClass

2) 如果(1)是不正确的,那么我认为每个虚方法都必须在使用它的子类中被覆盖,我的想法是对的吗?
并不是这样。virtual 是一种说法,“如果你想重写这个方法,那没问题”。实际上,我在上面的代码中包含了 LazyClass 来展示这一点。
上述内容不会导致编译器抱怨...
我没有经常使用接口,但那看起来像一个接口。在我的代码中,如果我改变...
    class ExtClass : BaseClass
    {
        public new string NewMethod()
        {
            return "NewMethod of ExtClass";
        }

        public override string VirtualOverrideMethod()
        {
            return "VirtualOverrideMethod of ExtClass";
        }
    }

to:

    class ExtClass : BaseClass
    {
        public new string NewMethod()
        {
            return "NewMethod of ExtClass";
        }

        public override string VirtualOverrideMethod()
        {
            return "VirtualOverrideMethod of ExtClass";
        }
    }

我得到:
error CS0501: 'OverrideTest.Program.ExtClass.VirtualOverrideMethod()' must declare a body because it is not marked abstract, extern, or partial

来自Visual Studio 2010。对于我的BaseClassvirtual方法也是如此。


0

好的,通过将方法声明为虚拟的(并且不希望在子类中覆盖该方法),如果您要在子类中引入一个同名的方法,那么您将进入方法隐藏的世界。您必须这样定义它:

public abstract class A
{
    public virtual string GetName()
    {
        return null;
    }
}

public class B : A
{
    //assume there are some defined properties.
    public new string GetName()
    {
        return FirstName;
    }
}

我建议您考虑抽象覆盖的方法:
public abstract class A
{
    public abstract string GetName()
}

// to override
public class B : A
{
    //assume there are some defined properties.
    public override string GetName()
    {
        return FirstName;
    }
}

这将会是非常不同的。我强烈建议您在子类中覆盖抽象方法。

然而,这里有一个关于该主题的快速参考(虽然旧了但很有价值):

http://www.akadia.com/services/dotnet_polymorphism.html


这段代码无法编译,你不能隐藏一个抽象方法。 - softveda
啊-好的,那么Class A必须是虚拟的。我会修改答案。 - jim tollan
实际上,您无法避免覆盖抽象方法。这是强制性的。 - Tigran

0

virtual 的需求是由多态性所决定的。想一想当你在子类中使用 new 重新定义一个方法时会发生什么:

class Parent
{
    void Foo() { Console.WriteLine("Parent"); }
    virtual void Bar { Console.WriteLine("Parent"); }
}

class Child : Parent
{
    new void Foo() { Console.WriteLine("Child"); } // another method Foo
    override void Bar { Console.WriteLine("Child"); }
}

var child = new Child();    // a Child instance
var parent = (Parent)child; // same object, but statically typed as Parent

c.Bar(); // prints "Child"
p.Bar(); // again, prints "Child" -- expected virtual behavior

c.Foo(); // prints "Child"
p.Foo(); // but this prints "Parent"!!

因此,旨在派生的类(即非sealed)应始终将可重写的方法标记为virtual,否则将出现混淆的运行时行为,如上所述。无论父实现是否实际执行任何操作都没有区别;关键是它表现不同

0

这就是virtualabstract方法之间的确切区别:

虚方法可以被派生类重写,如果选择这样做的话。虚方法可以有一个默认实现供继承者调用,使用base关键字来完成。

抽象方法必须被派生类覆盖。它的约束条件是:

  • 只能在抽象类中定义。
  • 不能有实现,因为具体行为由继承者定义。

0
1)如果一个方法没有实现,真的有必要将其定义为虚拟方法吗?在子类中重写它时,不使用virtual和override也可以吧?
您可以使用没有主体的抽象方法,但是如果您从该类派生,则必须覆盖该方法。 所有抽象方法都必须在子类中被覆盖。 2)如果(1)是不正确的,那么我是否正确地认为每个虚拟方法都必须在使用它的子类中被覆盖...
不,相反,您可以使用virtual关键字在其中具有实现,但不强制在子类中进行覆盖。从这个角度来看,这给您更多的灵活性。
通常,他们使用abstract创建刚性约束,以便每个从中派生的子代必须实现基类的指定成员。

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