8

I have the following situation:

In a 3rd party library (can not be modified):

class A { public virtual void M() {} }

class B : A { public override void M() {} }

In my own code:

class C : B { public override void M() {} }

From C's implementation of method M I want to call A's (but not B's!!). Can I?

Any tricks accepted, reflection included. I tried reflection already, but using the MethodInfo that I get from typeof(A) still generates a virtual call (calling C's implementation with subsequent stack overflow).

Deriving C from A is out of the question due to the complexity of reimplementing B.

Mau
  • 14,234
  • 2
  • 31
  • 52
  • This is one of the reasons I almost never use inheritance. – ChaosPandion Jul 31 '10 at 12:23
  • @ChaosPandion: Yeah! Totally! Come to think of it, why even write any code at all in the first place? – Timwi Jul 31 '10 at 12:36
  • @Timwi - I know you are just kidding but there are better ways such as composition. – ChaosPandion Jul 31 '10 at 12:45
  • @ChaosPandion: I know you are being serious but there are better hobbies such as sports. — What I’m getting at: use the right tool depending on your requirements. If you need virtual methods, you should use inheritance. If you need physical exercise, you should do sports. – Timwi Jul 31 '10 at 15:31
  • @Timwi - I did say *"almost never"* didn't I? :) – ChaosPandion Jul 31 '10 at 16:36
  • @ChaosPandion: Yes, you did, and it’s still stupid. Inheritance is the right tool for the job much more often than “almost never”. (Sorry about the noise, Mau — I’ll stop commenting here now.) – Timwi Aug 01 '10 at 08:51
  • @Timwi - One day, you'll realize that there is much more to the world of programming than object oriented design. *(You may also one day realize that you'll make more friends by not calling them stupid.)* – ChaosPandion Aug 01 '10 at 16:22
  • No guys, please keep going, I'm loving it :-) I must say inheritance is out at the moment, like pink. – Mau Aug 01 '10 at 18:27

5 Answers5

20

you can generate dynamic method to make proxy that use Call (not CallVirt) instruction

        var x = new C();
        var m = typeof (A).GetMethod("M");
        var dm = new DynamicMethod("proxy",  typeof (void), new [] {typeof(C)}, typeof (C));
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, m);
        il.Emit(OpCodes.Ret);
        var action = (Action<C>)dm.CreateDelegate(typeof (Action<C>));
        action(x);
desco
  • 16,642
  • 1
  • 45
  • 56
  • I had thought of IL non-virt call, but didn't think it would be so simple. Let me try :-) – Mau Jul 31 '10 at 12:43
  • 6
    This is clever, but if I saw this being used in production code to work around a limitation of architectural design, it would certainly raise my eyebrows. – Dan Bryant Jul 31 '10 at 14:36
2

In my previous answer I missed the fact that A and B are in an external library and cannot be modified. In that case, I would suggest a different approach. Basically, if the design flaw is in B, you can’t use B. Subclass from A instead.

The unfortunate consequence of this, of course, is that you may need to reimplement some or all of the functionality in B. You may be able to copy the code from Reflector if necessary. I realise that this sounds undesirable, but I still think it is preferable to using unmodifiable code that has a known issue that causes you problems.

Timwi
  • 65,159
  • 33
  • 165
  • 230
2

Quite old question but I had a similar problem recently. desco's answer gave the crucial hint. But the generated proxy delegate should be encapsulated in class C, can be static and initialized via static constructor so time consuming reflection will be required only once.

// -- in foreign assembly
class A { public virtual void M() { Console.WriteLine("A.M"); }}
class B : A {public override void M() { Console.WriteLine("B.M"); }}
//
// -- in own assembly
class C : B
{
    private static Action<C> call_A_M;

    public override void M() {
        call_A_M(this);
        Console.WriteLine("C.M");
    }

    static C()
    {
        var m = typeof(A).GetMethod("M");
        var dm = new DynamicMethod("", typeof(void), new[] { typeof(C) }, typeof(C));
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, m);
        il.Emit(OpCodes.Ret);
        call_A_M = (Action<C>)dm.CreateDelegate(typeof(Action<C>));
    }
}

But as Dan Bryant already mentioned: never ever use such dirty tricks in production code.

Merilix2
  • 476
  • 4
  • 5
1

I’m afraid this is not possible directly the way you describe — the purpose of virtual methods is for the overriding to be transparent. So the only way to do this at all is via a workaround.

Let me try to whip one up, but please be aware that this is a hacky suggestion. If you really need this construct in your code, it may be an indication that your code has a fundamental design flaw somewhere else, so restructuring something might be more desirable than filling it with yet another design flaw. But anyway, here goes...

class A {
    public virtual void M() { m_protected(); }
    protected void m_protected() { /* code goes here */ }
}

class B {
    public override void M() { /* code here, possibly invoking base.M() */ }
}

class C {
    public override void M() { m_protected(); }
}
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • Thanks @Timwi. As I say in the question, unfortunately A and B are part of a 3rd party library, so no changes allowed there. And yes, there is a design flaw: in class B! :-) – Mau Jul 31 '10 at 12:14
0

You cannot do that. You should probably design your class hierarchy differently, because it looks strange that C inherits from B, while behaving like A.

Anyway, it could make sense in your case. Then you should make another method in A which you will not override:

class A {
    protected virtual void basicM() {}
    public virtual void M() { basicM(); }
}
class C {
    public override void M() { basicM(); }
}

BTW, if you name the method as I did in the example, then you should probably rethink the whole thing. If this hierarchy is justified, than basicM probably performs something that deserves to be a separate method with a different name, perhaps even a public method.

zvone
  • 18,045
  • 3
  • 49
  • 77
  • Read the question :'( `A` and `B` can't be touched. I'm not asking 'How can I redesign this?' I'm asking how to call `A.M`. – Mau Jul 31 '10 at 12:27
  • If you are going to give me negative points, please provide a comment. Both I and everyone else would benefit from knowing what is wrong. – zvone Jul 31 '10 at 12:28
  • @Mau Read the first sentence of the answer! – zvone Jul 31 '10 at 12:29
  • This answer is pretty much identical to my earlier one, so I’m surprised you felt this one deserves a downvote but mine doesn’t. – Timwi Jul 31 '10 at 12:33