override 메서드가 정의된 타입의 인스턴스로 base 메서드를 호출하는 방법
말이 좀 어렵군요. 코드로 설명하는 것이 빠르겠지요? ^^ 다음과 같은 코드가 있을 때,
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace ConsoleApplication1
{
class BaseClass
{
public virtual void Do()
{
Console.WriteLine("BaseClass.Do");
}
}
class DerivedClass : BaseClass
{
public override void Do()
{
Console.WriteLine("DerivedClass.Do");
base.Do();
}
}
static class Program
{
static void Main(string[] args)
{
DerivedClass dc = new DerivedClass();
dc.Do();
}
}
}
실행하면 화면에는 다음과 같은 출력 결과가 나옵니다.
DerivedClass.Do
BaseClass.Do
그런데, DerivedClass.Do 메서드의 작업을 base.Do만 실행하도록 하는 방법은 없을까요? 당연히 virtual/override 메서드이기 때문에 다음과 같이 호출하는 것은 의도한 대로 동작하지 않습니다.
BaseClass dc = new DerivedClass();
dc.Do();
또한, 리플렉션을 어설프게 이용하는 수준으로는 마찬가지 결과만 얻게 됩니다.
MethodInfo mi = typeof(BaseClass).GetMethod("Do");
mi.Invoke(dc, null);
혹시나 싶어 검색을 해보았는데, 이미 누군가가 C# 3.0의 확장 메서드로 친절하게 만들어 놓았습니다. ^^
Invoke base method using reflection
; http://www.simplygoodcode.com/2012/08/invoke-base-method-using-reflection.html
위의 글에 공개된 소스코드는 다음과 같습니다.
public static object InvokeNotOverride(this MethodInfo methodInfo,
object targetObject, params object[] arguments)
{
var parameters = methodInfo.GetParameters();
if (parameters.Length == 0)
{
if (arguments != null && arguments.Length != 0)
throw new Exception("Arguments cont doesn't match");
}
else
{
if (parameters.Length != arguments.Length)
throw new Exception("Arguments cont doesn't match");
}
Type returnType = null;
if (methodInfo.ReturnType != typeof(void))
{
returnType = methodInfo.ReturnType;
}
var type = targetObject.GetType();
var dynamicMethod = new DynamicMethod("", returnType,
new Type[] { type, typeof(Object) }, type);
var iLGenerator = dynamicMethod.GetILGenerator();
iLGenerator.Emit(OpCodes.Ldarg_0); // this
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
iLGenerator.Emit(OpCodes.Ldarg_1); // load array argument
// get element at index
iLGenerator.Emit(OpCodes.Ldc_I4_S, i); // specify index
iLGenerator.Emit(OpCodes.Ldelem_Ref); // get element
var parameterType = parameter.ParameterType;
if (parameterType.IsPrimitive)
{
iLGenerator.Emit(OpCodes.Unbox_Any, parameterType);
}
else if (parameterType == typeof(object))
{
// do nothing
}
else
{
iLGenerator.Emit(OpCodes.Castclass, parameterType);
}
}
iLGenerator.Emit(OpCodes.Call, methodInfo);
iLGenerator.Emit(OpCodes.Ret);
return dynamicMethod.Invoke(null, new object[] { targetObject, arguments });
}
원리는 간단합니다. 런타임 시에 메서드를 바인딩하는 OpCodes.Callvirt 호출을 사용하지 않고 BaseClass.Do 메서드의 Handle 값을 그대로 OpCodes.Call 코드로 전달해 호출하는 동적 메서드를 만든 것입니다.
멋지군요. ^^ (
첨부 파일은 위의 예제코드를 테스트 한 것입니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]