Usando base
en C# sólo funciona para la base de inmediato. No puede acceder a un miembro base-base.
Parece que alguien más me golpeó hasta el golpe con la respuesta de que es posible hacerlo en IL.
Sin embargo, creo que la forma en que hice el código gen tiene algunas ventajas, así que lo publicaré de todos modos.
Lo que hice de manera diferente es usar árboles de expresiones, que le permiten usar el compilador de C# para realizar la resolución de sobrecarga y la sustitución de argumentos genéricos.
Eso es complicado, y no quiere tener que replicarlo usted mismo si puede evitarlo. En su caso, el código sería el siguiente:
var del =
CreateNonVirtualCall<Program, BaseClass, Action<ThirdClass>>
(
x=>x.SayNo()
);
Es probable que desee almacenar el delegado en un campo estático de sólo lectura, por lo que sólo tiene que compilar una vez.
es necesario especificar 3 argumentos genéricos:
El tipo de propietario - Esta es la clase que usted habría invocado el código de si no estuviera usando "CreateNonVirtualCall".
La clase base - Esta es la clase que desea realizar la llamada no virtual a partir de
Un tipo de delegado. Esto debería representar la firma del método que se está llamando con un parámetro adicional para el argumento "this". Es posible eliminar esto, pero requiere más trabajo en el método de código genético.
El método tiene un único argumento, una lambda que representa la llamada. Tiene que ser una llamada y solo una llamada. Si desea extender el gen del código, puede admitir cosas más complejas.
Por simplicidad, el cuerpo lambda está restringido a solo poder acceder a los parámetros lambda, y solo puede pasarlos directamente a la función. Puede eliminar esta restricción si amplía el código gen en el cuerpo del método para admitir todos los tipos de expresiones. Eso tomaría un poco de trabajo sin embargo. Puedes hacer lo que quieras con el delegado que vuelve, por lo que la restricción no es demasiado importante.
Es importante tener en cuenta que este código no es perfecto. Podría usar mucha más validación, y no funciona con los parámetros "ref" o "out" debido a las limitaciones del árbol de expresiones.
Lo probé en casos de muestra con métodos nulos, métodos de devolución de valores y métodos genéricos, y funcionó. Sin embargo, estoy seguro de que puede encontrar algunos casos extremos que no funcionan.
En cualquier caso, aquí está el código Gen IL:
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;
}
¿tenía intención de una de sus clases para heredar de BasClass (digamos de segunda clase)? –
No del todo; no hay más clases para agregar o cambiar ... – henry000