2010-07-31 2 views
7

я следующая ситуация:Как вызвать (не фактически) первоначальную реализацию виртуального метода?

В 3-сторонней библиотеки (не может быть изменен):

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

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

В моем собственном коде:

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

От реализации C «s метода M Я хочу позвонить A (но не B!). Могу я?

Любые трюки принимаются, отражение включены. Я уже пробовал отразить, но используя MethodInfo, который я получаю от typeof(A), по-прежнему генерирует виртуальный вызов (вызов C с последующим переполнением стека).

Получение C от A не может быть и речи из-за сложности переопределения B.

+0

Это одна из причин, почему я почти никогда не используют наследование. – ChaosPandion

+0

@ChaosPandion: Да! Полностью! Подумайте об этом, зачем вообще писать код вообще? – Timwi

+0

@ Тимви - Я знаю, что ты просто шутишь, но есть лучшие способы, такие как композиция. – ChaosPandion

ответ

16

вы можете создать динамический метод, чтобы сделать прокси, которые используют вызов (не CallVirt) инструкция

 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

Я думал об ИЛ-не-добровольном звонке, но не думал, что это будет так просто. Позвольте мне попробовать :-) – Mau

+0

Блестящий. Отлично работает. Ура! – Mau

+6

Это умно, но если бы я увидел, что это используется в производственном коде, чтобы обойти ограничения архитектурного дизайна, это наверняка поднимет мои брови. –

1

Я боюсь, что это невозможно сразу, как вы описали - цель виртуальных методов состоит в том, чтобы переопределение было прозрачным. Таким образом, единственный способ сделать это вообще - это обходной путь.

Позвольте мне попытаться взломать один, но, пожалуйста, помните, что это хакерское предложение. Если вам действительно нужна эта конструкция в вашем коде, это может свидетельствовать о том, что ваш код имеет фундаментальный недостаток дизайна где-то в другом месте, поэтому перестройка чего-то может быть более желательной, чем заполнение ее еще одним недостатком дизайна. Но в любом случае, здесь идет ...

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

Спасибо @Timwi. Как я уже сказал в этом вопросе, к сожалению, A и B являются частью сторонней библиотеки, поэтому никаких изменений там не было. И да, есть недостаток дизайна: в классе B! :-) – Mau

1

В моем предыдущем ответе я пропустил тот факт, что А и В во внешней библиотеке и не может быть изменен. В этом случае я бы предложил другой подход. В принципе, если дефект дизайна находится в B, вы не можете использовать B. Subclass from A вместо этого.

Несчастливое последствие этого, конечно, состоит в том, что вам может потребоваться переопределить некоторые или все функциональные возможности B. Вы можете, при необходимости, скопировать код из Reflector. Я понимаю, что это звучит нежелательно, но я все же думаю, что предпочтительнее использовать немодифицируемый код, который имеет известную проблему, которая вызывает проблемы.

+0

Это было бы неплохо, но есть TON нетривиального кода в 'B'. – Mau

0

Вы не можете этого сделать. Вы должны , вероятно, дизайн вашей иерархии классов по-разному, потому что выглядит странно, что C наследует от B, но ведет себя как A.

В любом случае, это может иметь смысл в вашем случае. Тогда вы должны сделать еще один метод в котором вы не перекроет:

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

BTW, если имя метода, как я сделал в примере, то вы, вероятно, следует пересмотреть всю вещь. Если эта иерархия оправдана, то basicM, вероятно, выполняет что-то, что заслуживает отдельного метода с другим именем, возможно, даже публичным методом.

+0

Задать вопрос: «(« А »и« В »не могут быть затронуты. Я не спрашиваю:« Как я могу переделать это? »Я спрашиваю, как называть« AM ». – Mau

+0

Если вы собираетесь дайте мне отрицательные моменты, пожалуйста, предоставьте комментарий. Мне и всем остальным было полезно узнать, что не так. – zvone

+0

@Mau Прочитайте первое предложение ответа! – zvone

Смежные вопросы