是否可以覆盖非虚方法?

时间:2009-12-05 23:45:38

标签: c# override overloading

有没有办法覆盖非虚方法?或者给出类似结果的东西(除了创建一个新的方法来调用所需的方法)?

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

9 个答案:

答案 0 :(得分:102)

不,您无法覆盖非虚方法。您可以做的最接近的事情是通过创建具有相同名称的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方法都打印Foo.M。正如您所看到的,只要对该对象的引用具有正确的派生类型但隐藏基本方法 打破多态,这种方法确实允许您为方法创建一个新实现。

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

我倾向于支持那些支持C#默认行为的人,默认情况下这些方法是非虚拟的(而不是Java)。我会更进一步说,默认情况下也应该密封类。继承很难正确设计,并且存在一个未标记为虚拟的方法这一事实表明该方法的作者从未想过要覆盖该方法。

编辑:“执行时间多态分派”

我的意思是,当您调用虚拟方法时,在执行时发生的默认行为。比方说,在我之前的代码示例中,我确实定义了一个虚方法和一个真正的重写方法,而不是定义一个非虚方法。

如果我在这种情况下调用b.Foo,CLR将正确地确定b引用指向Bar的对象类型,并将调用发送到{{ 1}}恰当。

答案 1 :(得分:21)

不,你不能。

您只能覆盖虚拟方法 - 请参阅MSDN here

  

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

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

答案 2 :(得分:4)

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

答案 3 :(得分:2)

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

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

从来没有任何事情阻止您向现有类添加重载,但只有知道您的类的代码才能访问它。

答案 4 :(得分: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();

    }
}

答案 5 :(得分:0)

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

答案 6 :(得分:0)

  

有没有办法覆盖非虚方法?或者给出类似结果的东西(除了创建一个新的方法来调用所需的方法)?

您无法覆盖非虚拟方法。但是,您可以使用new修饰符关键字来获得类似的结果:

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 modifier也相同,否则您将无法获得继承权。如果另一个类继承自Class1,则new中的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());

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

答案 7 :(得分:0)

这个问题的答案并不完全否定。这取决于您如何构建继承和访问类的实例。如果您的设计满足以下条件,您将能够使用 new 修饰符覆盖基类中的非虚拟方法:

  • 该方法在您的类继承的接口中声明
  • 您正在使用接口访问类实例

以下面的例子为例:

interface ITest { void Test(); }
class A : ITest { public void Test(){Console.WriteLine("A");} }
class B : A { public new void Test(){Console.WriteLine("B");} }
ITest x = new B(); 
x.Test(); // output "A"

调用 x.Test() 将输出“A”到控制台。但是,如果您在 class B 的定义中重新声明接口,x.Test() 将改为输出 B。

interface ITest { void Test(); }
class A : ITest { public void Test(){Console.WriteLine("A");} }
class B : A, ITest { public new void Test(){Console.WriteLine("B");} }
ITest x = new B();
x.Test(); // output "B"

答案 8 :(得分:-5)

有一种方法可以使用抽象类和抽象方法实现这一目的。

考虑

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

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

现在,如果您希望使用不同版本的方法MethodToBeTested(),那么 将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    
    }

}

现在你有2个类实现(覆盖)MethodToBeTested()

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