Использование base
в C # работает только для непосредственной базы. Вы не можете получить доступ к элементу base-base.
Похоже, кто-то другой избил меня до отказа, ответив, что это можно сделать в IL.
Тем не менее, я думаю, что способ, которым я сделал code gen, имеет некоторые преимущества, поэтому я все равно опубликую его.
То, что я сделал по-другому, - это использование деревьев выражений, которые позволяют использовать компилятор C # для разрешения перегрузки и подстановки общих аргументов.
Эта штука сложная, и вам не нужно копировать ее самостоятельно, если вы можете ей помочь.
В вашем случае код будет работать так:
var del =
CreateNonVirtualCall<Program, BaseClass, Action<ThirdClass>>
(
x=>x.SayNo()
);
Возможно, вы захотите сохранить делегат в статическом поле, предназначенном только для чтения, так что вам придется скомпилировать его только один раз.
Вам необходимо указать 3 общих аргумента:
Тип владельца - это класс, из которого вы бы вызвали код, если бы не использовали «CreateNonVirtualCall».
Базовый класс - это класс, с которого вы хотите сделать не виртуальный вызов
Тип делегата. Это должно представлять сигнатуру вызываемого метода с дополнительным параметром для аргумента this. Это можно устранить, но это требует больше работы в методе code gen.
Метод принимает один аргумент - лямбда, представляющую вызов. Это должен быть звонок, и только звонок. Если вы хотите расширить код поколения, вы можете поддерживать более сложные вещи.
Для простоты лямбда-тело ограничено только возможностью доступа к лямбда-параметрам и может только передавать их непосредственно в функцию. Вы можете снять это ограничение, если вы расширите код gen в теле метода для поддержки всех типов выражений. Это займет некоторую работу, хотя. С возвращающимся делегатом вы можете делать все, что захотите, поэтому ограничение не слишком велико.
Важно отметить, что этот код не идеален. Он может использовать гораздо больше проверок и не работает с параметрами «ref» или «out» из-за ограничений дерева выражений.
Я проверил его в примерах с помощью методов void, методов, возвращающих значения, и универсальных методов, и это сработало. Однако я уверен, что вы можете найти некоторые крайние случаи, которые не работают.
В любом случае, вот код IL Gen:
public static TDelegate CreateNonVirtCall<TOwner, TBase, TDelegate>(Expression<TDelegate> call) where TDelegate : class
{
if (! typeof(Delegate).IsAssignableFrom(typeof(TDelegate)))
{
throw new InvalidOperationException("TDelegate must be a delegate type.");
}
var body = call.Body as MethodCallExpression;
if (body.NodeType != ExpressionType.Call || body == null)
{
throw new ArgumentException("Expected a call expression", "call");
}
foreach (var arg in body.Arguments)
{
if (arg.NodeType != ExpressionType.Parameter)
{
//to support non lambda parameter arguments, you need to add support for compiling all expression types.
throw new ArgumentException("Expected a constant or parameter argument", "call");
}
}
if (body.Object != null && body.Object.NodeType != ExpressionType.Parameter)
{
//to support a non constant base, you have to implement support for compiling all expression types.
throw new ArgumentException("Expected a constant base expression", "call");
}
var paramMap = new Dictionary<string, int>();
int index = 0;
foreach (var item in call.Parameters)
{
paramMap.Add(item.Name, index++);
}
Type[] parameterTypes;
parameterTypes = call.Parameters.Select(p => p.Type).ToArray();
var m =
new DynamicMethod
(
"$something_unique",
body.Type,
parameterTypes,
typeof(TOwner)
);
var builder = m.GetILGenerator();
var callTarget = body.Method;
if (body.Object != null)
{
var paramIndex = paramMap[((ParameterExpression)body.Object).Name];
builder.Emit(OpCodes.Ldarg, paramIndex);
}
foreach (var item in body.Arguments)
{
var param = (ParameterExpression)item;
builder.Emit(OpCodes.Ldarg, paramMap[param.Name]);
}
builder.EmitCall(OpCodes.Call, FindBaseMethod(typeof(TBase), callTarget), null);
if (body.Type != typeof(void))
{
builder.Emit(OpCodes.Ret);
}
var obj = (object) m.CreateDelegate(typeof (TDelegate));
return obj as TDelegate;
}