是否可以覆盖非虚拟方法?


90

有什么方法可以覆盖非虚拟方法?或产生类似结果的东西(除了创建一个新方法来调用所需的方法之外)?

我想Microsoft.Xna.Framework.Graphics.GraphicsDevice在考虑单元测试的情况下覆盖其中的一种方法。


8
你的意思是过载超控?重载=添加一个具有相同名称但参数不同的方法(例如,Console.WriteLine的重载不同)。重写=(大致)更改方法的默认行为(例如Shape.Draw方法,其对Circle,Rectangle等的行为不同)。您始终可以在派生类中重载方法,但重写仅适用于虚拟方法。
itowlson

Answers:


111

不可以,您不能覆盖非虚拟方法。您可以做的最接近的事情是通过创建一个new具有相同名称的方法来隐藏该方法,但这是不可取的,因为它违反了良好的设计原则。

但是,即使隐藏方法也不会像真正的虚拟方法调用那样为您提供执行时间。考虑以下示例:

using System;

class Example
{
    static void Main()
    {
        Foo f = new Foo();
        f.M();

        Foo b = new Bar();
        b.M();
    }
}

class Foo
{
    public void M()
    {
        Console.WriteLine("Foo.M");
    }
}

class Bar : Foo
{
    public new void M()
    {
        Console.WriteLine("Bar.M");
    }
}

在此示例中,两个M方法都调用print Foo.M。如您所见,只要对该对象的引用具有正确的派生类型,但此方法的确可以为该方法提供新的实现,但是隐藏基本方法确实会破坏多态性。

我建议您不要以这种方式隐藏基本方法。

我倾向于支持C#默认行为的那些人,即默认情况下方法是非虚拟的(与Java相对)。我还要说的是,默认情况下也应该密封类。很难为继承进行适当的设计,并且存在一个没有被标记为虚拟的方法的事实表明该方法的作者从未打算重写该方法。

编辑:“执行时间多态调度”

我的意思是这是调用虚拟方法时在执行时发生的默认行为。举例来说,在我之前的代码示例中,实际上没有定义虚拟方法,而是定义了虚拟方法,而不是定义非虚拟方法。

如果要b.Foo在这种情况下进行调用,则CLR将正确确定b引用所指向的对象的类型,BarM适当地将调用分派给该对象。


6
尽管从技术上讲“执行时间多态调度”是正确的说法,但我想这可能几乎遍及所有人!
Orion Edwards,

3
尽管作者可能确实打算禁止方法重写,但这并不是正确的做法。我认为XNA团队应该已经实现了IGraphicsDevice接口,以使用户在调试和单元测试方面更具灵活性。我被迫做一些非常丑陋的事情,这应该是团队所预见的。进一步的讨论可以在这里找到:forums.xna.com/forums/p/30645/226222.aspx
zfedoran

2
@Orion我也听不懂,但是在浏览了一下Google之后,我很高兴看到“正确的”术语的使用。
zfedoran

9
我完全不赞成“作者不打算这样做”的说法。这就像说车轮的发明者没有想到车轮会被安装在汽车上,因此我们不能在汽车上使用它们。我知道方向盘/方法的工作原理,我想做一些稍微不同的事情。我不在乎创作者是否想要我。很抱歉尝试恢复死线程。在找出解决这个问题的真正不便的方法之前,我只需要吹一些
Jeff

2
那么,当您继承大型代码库并需要添加新功能测试时该怎么办,但是为了测试您需要实例化大量机器,该怎么办?在Java中,我只是扩展这些部分并覆盖存根所需的方法。在C#中,如果方法未标记为虚拟方法,则我必须找到其他机制(模拟库或类似的机制)。
亚当·帕金

22

不,你不能。

您只能覆盖虚拟方法-在此处查看MSDN

在C#中,派生类可以包含与基类方法同名的方法。

  • 基类方法必须虚拟定义。

6

如果未密封基类,则可以从基类继承并编写一个隐藏基类的新方法(在方法声明中使用“ new”关键字)。否则,您不能覆盖它,因为它不是原作者意图覆盖它,因此为什么它不是虚拟的。


3

我认为您正在重载并覆盖混乱,重载意味着您​​拥有两个或多个具有相同名称但参数集不同的方法,而重载则意味着您对派生类中的方法具有不同的实现(从而替换或修改了行为在它的基类中)。

如果方法是虚拟的,则可以使用派生类中的override关键字覆盖它。但是,非虚拟方法只能通过使用new关键字代替override关键字来隐藏基本实现。如果调用者通过类型为基类型的变量访问该方法,则非虚拟路由将无用,因为编译器将对基方法使用静态分派(这意味着将永远不会调用派生类中的代码)。

从来没有什么可以阻止您向现有类添加重载,但是只有知道您的类的代码才能访问它。


2

您不能覆盖C#中任何类的非虚拟方法(不对CLR进行黑客攻击),但是可以覆盖该类实现的接口的任何方法。考虑我们没有密封

class GraphicsDevice: IGraphicsDevice {
    public void DoWork() {
        Console.WriteLine("GraphicsDevice.DoWork()");
    }
}

// with its interface
interface IGraphicsDevice {
    void DoWork();
}

// You can't just override DoWork in a child class,
// but if you replace usage of GraphicsDevice to IGraphicsDevice,
// then you can override this method (and, actually, the whole interface).

class MyDevice: GraphicsDevice, IGraphicsDevice {
    public new void DoWork() {
        Console.WriteLine("MyDevice.DoWork()");
        base.DoWork();
    }
}

这是演示

class Program {
    static void Main(string[] args) {

        IGraphicsDevice real = new GraphicsDevice();
        var myObj = new MyDevice();

        // demo that interface override works
        GraphicsDevice myCastedToBase = myObj;
        IGraphicsDevice my = myCastedToBase;

        // obvious
        Console.WriteLine("Using real GraphicsDevice:");
        real.DoWork();

        // override
        Console.WriteLine("Using overriden GraphicsDevice:");
        my.DoWork();

    }
}


0

如果您要从非派生类继承,则可以简单地创建一个抽象超类并从下游继承它。


0

有什么方法可以覆盖非虚拟方法?或产生类似结果的东西(除了创建一个新方法来调用所需的方法之外)?

您不能覆盖非虚拟方法。但是,您可以使用newmodifier关键字获得类似的结果:

class Class0
{
    public int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    public new int Test()
    {
        return 1;
    }
}
. . .
// result of 1
Console.WriteLine(new Class1().Test());

您还需要确保access修饰符也相同,否则您将无法获得继承。如果从另一个类继承Class1new关键字Class1不会影响对象从它继承,除非访问修饰符是一样的。

如果访问修饰符相同:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // different access modifier
    new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 0
Console.WriteLine(new Class2().Result());

...如果与访问修饰符一样的:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // same access modifier
    protected new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 1
Console.WriteLine(new Class2().Result());

如先前的回答所指出,这不是一个好的设计原则。


-5

有一种使用抽象类和抽象方法来实现此目的的方法。

考虑

Class Base
{
     void MethodToBeTested()
     {
        ...
     }

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

现在,如果希望使用不同版本的MethodMethodToBeTested()方法,则将Class Base更改为抽象类,并将MethodToBeTested()方法更改为抽象方法

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

随着抽象void MethodToBeTested()的出现;实施不见了。

因此,创建一个class DefaultBaseImplementation : Base具有默认实现的。

并创建另一个class UnitTestImplementation : Base具有单元测试的实现。

使用这两个新类,可以覆盖基类功能。

Class DefaultBaseImplementation : Base    
{
    override void MethodToBeTested()    
    {    
        //Base (default) implementation goes here    
    }

}

Class UnitTestImplementation : Base
{

    override void MethodToBeTested()    
    {    
        //Unit test implementation goes here    
    }

}

现在,您有两个实现(覆盖)的类MethodToBeTested()

您可以根据需要实例化(派生的)类(即使用基本实现或单元测试实现)。


@slavoo:嗨,slavoo。感谢您的代码更新。但是,能否请您告诉我对此否决的原因?
ShivanandSK

6
因为它不能回答问题。他在问您是否可以覆盖未标记为虚拟的成员。您已经证明必须实现标记为抽象的成员。
Lee Louviere
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.