可以重写非虚方法吗?

有没有办法重写一个非虚方法?或者给出类似的结果(除了创建一个新的方法来调用所需的方法之外) ?

考虑到单元测试,我想重写 Microsoft.Xna.Framework.Graphics.GraphicsDevice中的一个方法。

92718 次浏览

No, you cannot override a non-virtual method. The closest thing you can do is hide the method by creating a new method with the same name but this is not advisable as it breaks good design principles.

But even hiding a method won't give you execution time polymorphic dispatch of method calls like a true virtual method call would. Consider this example:

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");
}
}

In this example both calls to the M method print Foo.M. As you can see this approach does allow you to have a new implementation for a method as long as the reference to that object is of the correct derived type but hiding a base method does break polymorphism.

I would recommend that you do not hide base methods in this manner.

I tend to side with those who favor C#'s default behavior that methods are non-virtual by default (as opposed to Java). I would go even further and say that classes should also be sealed by default. Inheritance is hard to design for properly and the fact that there is a method that is not marked to be virtual indicates that the author of that method never intended for the method to be overridden.

Edit: "execution time polymorphic dispatch":

What I mean by this is the default behavior that happens at execution time when you call virtual methods. Let's say for example that in my previous code example, rather than defining a non-virtual method, I did in fact define a virtual method and a true overridden method as well.

If I were to call b.Foo in that case, the CLR would correctly determine the type of object that the b reference points to as Bar and would dispatch the call to M appropriately.

No you can't.

You can only override a virtual method - see the MSDN here:

In C#, derived classes can contain methods with the same name as base class methods.

  • The base class method must be defined virtual.

If the base class isn't sealed then you can inherit from it and write a new method that hides the base one (use the "new" keyword in the method declaration). Otherwise no, you cannot override it because it was never the original authors intent for it to be overridden, hence why it isn't virtual.

I think you're getting overloading and overriding confused, overloading means you have two or more methods with the same name but different sets of parameters while overriding means you have a different implementation for a method in a derived class (thereby replacing or modifying the behaviour in it's base class).

If a method is virtual, you can override it using the override keyword in the derrived class. However, non-virtual methods can only hide the base implementation by using the new keyword in place of the override keyword. The non-virtual route is useless if the caller accesses the method via a variable typed as the base type as the compiler would use a static dispatch to the base method (meaning the code in your derrived class would never be called).

There is never anything preventing you from adding an overload to an existing class, but only code that knows about your class would be able to access it.

There is a way of achieving this using abstract class and abstract method.

Consider

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


void Method1()
{
}


void Method2()
{
}


...
}

Now, if you wish to have different versions of method MethodToBeTested(), then change Class Base to an abstract class and method MethodToBeTested() as an abstract method

abstract Class Base
{


abstract void MethodToBeTested();


void Method1()
{
}


void Method2()
{
}


...
}

With abstract void MethodToBeTested() comes an issue; the implementation is gone.

Hence create a class DefaultBaseImplementation : Base to have the default implementation.

And create another class UnitTestImplementation : Base to have unit test implementation.

With these 2 new classes, the base class functionality can be overridden.

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


}


Class UnitTestImplementation : Base
{


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


}

Now you have 2 classes implementing (overriding) MethodToBeTested().

You can instantiate the (derived) class as required (i.e. either with base implementation or with unit test implementation).

In the case you are inheriting from a non-derived class, you could simply create an abstract super class and inherit from it downstream instead.

Is there any way to override a non-virtual method? or something that gives similar results (other than creating a new method to call the desired method)?

You cannot override a non-virtual method. However you can use the new modifier keyword to get similar results:

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


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

You will also want to make sure that the access modifier is also the same, otherwise you will not get inheritance down the line. If another class inherits from Class1 the new keyword in Class1 will not affect objects inheriting from it, unless the access modifier is the same.

If the access modifier is not the same:

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());

...versus if the access modifier is the same:

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());

As pointed out in a previous answer, this is not a good design principle.

You can't override non-virtual method of any class in C# (without hacking CLR), but you can override any method of interface the class implements. Consider we have non-sealed

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();
}
}

And here's demo

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();


}
}

The answer to this question is not entirely No. It depends on how you structure your inheritance and access the instances of your classes. If your design meets the following, you would be able to override non-virtual method from base class with the new modifier:

  • The method is declared in an interface that your classes inherit from
  • You are accessing the class instances using the interface

Take example of the following:

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"

calling x.Test() will output "A" to the console. However if you re-declare the interface in the definition of class B, x.Test() will output B instead.

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"