2010-07-31 8 views
7

Ich habe die folgende Situation:Wie (virtuell) wird die ursprüngliche Implementierung einer virtuellen Methode aufgerufen?

In einer 3rd-Party-Bibliothek (kann nicht geändert werden):

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

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

In meinem eigenen Code:

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

Von C ‚s Implementierung von Verfahren M Ich möchte A 's anrufen (aber nicht B ist !!). Kann ich?

Alle Tricks akzeptiert, Reflexion enthalten. Ich habe bereits die Reflektion versucht, aber unter Verwendung der MethodInfo, die ich von typeof(A) bekomme, wird immer noch ein virtueller Anruf erzeugt (Aufruf der C Implementierung mit anschließendem Stapelüberlauf).

Die Ableitung C von A kommt aufgrund der Komplexität der Reimplementierung B nicht in Frage.

+0

Dies ist einer der Gründe, warum ich so gut wie nie Vererbung. – ChaosPandion

+0

@ChaosPandion: Ja! Total! Wenn ich darüber nachdenke, warum überhaupt irgendeinen Code überhaupt schreiben? – Timwi

+0

@Timwi - Ich weiß, du machst nur Spaß, aber es gibt bessere Möglichkeiten wie Zusammensetzung. – ChaosPandion

Antwort

16

Sie dynamische Methode erzeugen können Proxy zu machen, den Anruf verwendet (nicht callvirt) Anweisung

 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); 
+0

Ich hatte an einen IL-non-virt-Anruf gedacht, aber ich dachte nicht, dass es so einfach wäre. Lass mich es versuchen :-) – Mau

+0

Brilliant. Es wirkt wie ein Zauber. Prost! – Mau

+6

Das ist schlau, aber wenn ich sehe, dass dies im Produktionscode verwendet wird, um eine Einschränkung des architektonischen Designs zu umgehen, würde es sicherlich meine Augenbrauen heben. –

1

Ich fürchte, das ist nicht direkt möglich, wie Sie es beschreiben - der Zweck der virtuellen Methoden ist, dass das Overriding transparent ist. Der einzige Weg, dies zu tun, ist über einen Workaround.

Lassen Sie mich versuchen, eine zu peitschen, aber bitte beachten Sie, dass dies ein hacky Vorschlag ist. Wenn Sie dieses Konstrukt wirklich in Ihrem Code benötigen, kann dies ein Hinweis darauf sein, dass Ihr Code an anderer Stelle einen grundlegenden Konstruktionsfehler aufweist. Eine Umstrukturierung ist daher möglicherweise wünschenswerter, als sie mit einem weiteren Konstruktionsfehler zu füllen. Aber wie auch immer, hier geht ...

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

Danke @Timwi. Wie ich in der Frage sage, sind A und B leider Teil einer 3rd Party Library, also keine Änderungen erlaubt. Und ja, es gibt einen Designfehler: in Klasse B! :-) – Mau

1

In meiner vorherigen Antwort vermisste ich die Tatsache, dass A und B in einer externen Bibliothek ist und nicht geändert werden kann. In diesem Fall würde ich einen anderen Ansatz vorschlagen. Wenn der Designfehler in B liegt, können Sie stattdessen nicht B. Subklasse aus A verwenden.

Die unglückliche Folge davon ist natürlich, dass Sie möglicherweise einige oder alle der Funktionalität in B neu implementieren müssen. Sie können möglicherweise den Code von Reflector bei Bedarf kopieren. Ich weiß, dass dies unerwünscht klingt, aber ich denke immer noch, dass es vorzuziehen ist, nicht modifizierbaren Code zu verwenden, der ein bekanntes Problem hat, das zu Problemen führt.

+0

Das wäre nett, aber es gibt eine Menge nicht-trivialen Code in 'B'. – Mau

0

Sie können das nicht tun. Sie sollten wahrscheinlich entwerfen Sie Ihre Klassenhierarchie anders, weil es seltsam aussieht, dass C von B erbt, während sich wie A.

Wie auch immer, könnte es in Ihrem Fall Sinn machen. Dann sollten Sie eine andere Methode in A, die machen Sie nicht außer Kraft setzen:

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

BTW, wenn Sie den Namen der Methode, wie ich in dem Beispiel tat, dann sollten Sie vielleicht die ganze Sache überdenken. Wenn diese Hierarchie gerechtfertigt ist, dann führt basicM wahrscheinlich etwas, das eine separate Methode mit einem anderen Namen verdient, vielleicht sogar eine öffentliche Methode.

+0

Lesen Sie die Frage: "(' A' und 'B' können nicht berührt werden. Ich frage nicht 'Wie kann ich das umgestalten?' Ich frage, wie man' AM' nennt. – Mau

+0

Wenn du gehst Geben Sie mir negative Punkte, bitte geben Sie einen Kommentar ab. Sowohl ich als auch alle anderen würden davon profitieren zu wissen, was falsch ist. – zvone

+0

@Mau Lesen Sie den ersten Satz der Antwort! – zvone

Verwandte Themen