[UPDATED] How to call the method from the base of the base of the current class ? (base.base.MyMethod)
11 May 2011Today I encountered a tricky need in some custom control. It was inheriting from the TabControl but I didn’t want all its feature. Especially I didn’t want it to update the SelectedContent dependency property because it was keeping a strong reference to a ViewModel and keeping it away from the garbage collector.
The problem is that the TabControl do this in an event handler of the ItemContainerGenerator’s StatusChanged event. This subscription is made in the OnInitialized method. This one is marked as protected and virtual so I could override it to do nothing but if had do so I would have loose all the work done in the base class of the TabControl which is Selector. So I needed to be able to call the Selector base method and skip the one of the TabControl. Unfortunately you can’t access to a method base class of the base class of your current instance.
In this post we’ll see how it can be performed anyway via a tricky technique. Truth to be told, I found this solution on stack overflow and I found it so neat that I needed to share it with you !
Note: the title may be rephrased to “How to skip an override in a method inheritance path”.
Edit: thanks to Thomas Levesque there is now two solutions presented here !
First solution
Let’s say that we have three classes : GrandFather, Father and Child.
We want to be abble to call the MyMethod of the GrandFather from the MyMethod of the child without executing the body of the Father’s one. Each of this method only update the MyProperty method telling which method is executed. For example here is the body of the grandfather method:
public virtual void MyMethod(object param) { MyProperty = "I am set from the GrandFather class"; }
In the MyMethod of the Child, we usually call the base method using the base operator as in this snippet:
public override void MyMethod(object param) { base.MyMethod(param); }
It works like a charm but in our case, what we actually need is to call base.base.MyMethod() but this is not possible in C#.
The solution is so to create a delegate, which, as Lassaad told me is a pointer to a function, and update it’s target to be our current instance. To create the delegate we need an instance of the GrandFather class so the aimed function is the good one on the grandFather class. Then we perform a little reflection magic to update the field of the Delegate representing its target so it become our current instance. Here is the snippet of code doing all this :
public override void MyMetdhod(object param) { //base.MyMethod(param); NO MORE ! //Create an instance of the grandFather GrandFather grandFather = new GrandFather(); //Create a delegate to its "MyMethod" method var createdDelegate = Delegate.CreateDelegate( typeof(MyMethodDelegate), grandFather, "MyMethod" ); //Change the target of the delegate to be the current instance typeof(MyMethodDelegate).BaseType.BaseType.GetField( "_target", BindingFlags.Instance | BindingFlags.NonPublic) .SetValue(createdDelegate, this); //Invoke the delegate createdDelegate.DynamicInvoke(param); }
In the case of the TabControl it was even harder because the grand father class is Selector which is actually abstract and can’t be instanced. The solution is to create a “fake” class just for the purpose of this trick. It can then be used and instanced when needed:
public class FakeClass : Selector { }
Here an example of this working in a WPF application (code source is attached):
Second solution
As pointed out by Thomas Levesque in the comments, the solution I first proposed have several drawbacks:
- We have to create an instance of the grand father class just to be abble to create the first delegate,
- We use reflection to set an undocumented field on the Delegate class which can disapear in the next version of the framework.
The solution he proposes is using the IL generator to create a dynamic method. This technique free us of the creation of a dummy instance which is better for performance and without side-effects... Here is the code to use in the Child class:
private delegate void MyMethodDelegate(GrandFather instance, object e); private static readonly MyMethodDelegate myMethodDelegateRealMethod;
static Child() { DynamicMethod dm = new DynamicMethod( "GrandFatherMyMethod", typeof(void), new Type[] { typeof(GrandFather), typeof(object) }, typeof(Child)); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.EmitCall(OpCodes.Call, typeof(GrandFather) .GetMethod("MyMethod", BindingFlags.Instance | BindingFlags.NonPublic), null); il.Emit(OpCodes.Ret); myMethodDelegateRealMethod=
(MyMethodDelegate)dm.CreateDelegate(typeof(MyMethodDelegate)); }
private override void MyMethod(EventArgs e) { myMethodDelegateRealMethod(this, e);
}
Be sure to notice that this is the Call operator which is used instead of the CallVirt which would have result in an infinte call loop.
Don't forget to visit his blog : http://tomlev2.wordpress.com/
- By JonathanANTOINE@falsemail.com
- - .NET
- - Tags :
Comments
Interesting...
.
Normally, you should never need to call base.base.SomeMethod, it's usually a sign of a bad design because you're not supposed to be aware of implementation details of the base class. But of course in the case of the TabControl you're not in control of the base classes, so it's different...
.
Anyway, I don't really like this solution... Two main reasons :
- you have to create a dummy instance of the class, which is pretty bad for performance and could have unexpected side effects
- it uses reflection (also bad for performance) on a non documented private field of the Delegate class. The implementation of this class could change in future versions, and this solution wouldn't work any more.
.
What you need to achieve this is a non-virtual call to GrandFather.MyMethod. This can be done by generating code at runtime with DynamicMethod:
(note the use of OpCodes.Call instead of OpCodes.Callvirt)
@Thomas Levesque :Hello Thomas,
Thank you for your comment and it works like a charm !
Btw, there is still reflection in your solution, the only difference is that we use 'this' as the current instance without recreating one.
I will update my post to add your solution
Thx a lot !