How to force call a C# derived method

2019-04-06 22:32发布

问题:

I have a class that is generated by some tool, therefore I can't change it. The generated class is very simple (no interface, no virtual methods):

class GeneratedFoo
{
  public void Write(string p) { /* do something */ }
}

In the C# project, we want to provide a way so that we can plug in a different implementation of MyFoo. So I'm thinking to make MyFoo derived from GeneratedFoo

class MyFoo : GeneratedFoo
{
  public new void Write(string p) { /* do different things */ }
}

Then I have a CreateFoo method that will either return an instance of GeneratedFoo or MyFoo class. However it always calls the method in GeneratedFoo.

GeneratedFoo foo = CreateFoo(); // if this returns MyFoo,
foo.Write("1"); // it stills calls GeneratedFoo.Write

This is expceted since it is not a virtual method. But I'm wondering if there is a way (a hack maybe) to make it call the derived method.

Thanks,
Ian

回答1:

Without being able to make the method virtual, no. A non-virtual method is statically linked at compile time and can't be changed.



回答2:

Adam gave you an answer (correct one). Now it's time for hack you were asking for :)


class BaseFoo
{
    public void Write() { Console.WriteLine("Hello simple world"); }
}

class DerFoo : BaseFoo
{
    public void Write() { Console.WriteLine("Hello complicated world"); }
}

public static void Main()
{
    BaseFoo bs = new DerFoo();
    bs.Write();

    bs.GetType().GetMethod("Write").Invoke(bs, null);
}

Prints out:

Hello simple world
Hello complicated world


回答3:

Write an extension method that safecasts to your derived type, and calls the method against that reference instead.

public static class Extensions
{
  public static void WriteFoo(this GeneratedFoo foo, string p)
  {
     MyFoo derived = foo as MyFoo;
     if (derived != null)
     {
        derived.Write(p);
     }
     else
     {
        foo.Write(p);
     }
  }
}

then call it with

GeneratedFoo unknownFoo;
unknownFoo.WriteFoo("win");

NB: this is a dirty hack. A clearer solution would be to ask yourself why you need to use the new modifier in the first place. Overloading the meaning of Write(p) will make your code confusing for maintainers. You could just as easily declare it public void WriteToTarget() or something much more specific to avoid that confusion in the first place.



回答4:

Using "new" on a method is not overriding, its hiding the original method. This is completely different to polymorphism and should be avoided, its only purpose is for covariance/contra-variance of methods that perform an identical function (eg, public Foo Clone(); and public new Bar Clone();). The other purpose is for legacy code when an identically named method is added to a base class that you have no control over and cannot at this time change your method's name.

Though the two methods share the same name they are completely separate methods, it occupies a different slot in the class's method table, where as overriding replaces the existing method in a class's method table.

My solution to this would be to use an interface such as IFoo, and either edit the generated class or use a proxy class that delegates all its method calls to an instance of GeneratedFoo to implement IFoo.


public DelegatingFoo : IFoo
{
  private GeneratedFoo foo;

  public DelegatingFoo(GeneratedFoo foo) { this.foo = foo; }
  public void Write(string p) { foo.Write(p); }
}


回答5:

If the generated class has no virtual method then it is not possible. If you could modify the tool to generate the Write method as virtual then in MyFoo use the keyword override instead of new to qualify the Write method. A second option would be to write a script/macro to change the source code of GeneratedFoo after the tool has run to inject the string "virtual" before desired methods as part of your build process.



回答6:

What about a custom FX cop rule added to your build system?



回答7:

An approach that you could use would be to actually define a few new classes all derived from GeneratedFoo; eg:

public class MyFooBase : GeneratedFoo
{
    public virtual void MyMethod() { ... }
}

public class MyFoo1 : MyFooBase { ... }

public class MyFoo2 : MyFooBase { ... }

This would allow you to essentially add virtual methods to your generated class.