Uso base
in C# funziona solo per la base immediata. Non è possibile accedere a un membro base-base.
Sembra che qualcun altro mi abbia picchiato al punzone con la risposta riguardo a ciò che è possibile fare in IL.
Tuttavia, penso che il modo in cui ho creato il codice gen abbia alcuni vantaggi, quindi lo posterò comunque.
La cosa che ho fatto in modo diverso è utilizzare gli alberi di espressione, che consentono di utilizzare il compilatore C# per eseguire la risoluzione di sovraccarico e la sostituzione di argomenti generici.
Quella roba è complicata, e non si vuole dover replicare se stessi se si può aiutare. Nel tuo caso, il codice dovrebbe funzionare in questo modo:
var del =
CreateNonVirtualCall<Program, BaseClass, Action<ThirdClass>>
(
x=>x.SayNo()
);
si sarebbe probabilmente desidera memorizzare il delegato in un campo statico di sola lettura, in modo da avere solo compilare una volta.
è necessario specificare 3 argomenti generici:
Il tipo proprietario - Questa è la classe che avrebbe invocato il codice se non stava utilizzando "CreateNonVirtualCall".
La classe di base - Questa è la classe che si desidera effettuare la chiamata non virtuale da
Un tipo delegato. Questo dovrebbe rappresentare la firma del metodo che viene chiamato con un parametro extra per l'argomento "this". È possibile eliminarlo, ma richiede più lavoro nel metodo del codice gen.
Il metodo accetta un singolo argomento, un lambda che rappresenta la chiamata. Deve essere una chiamata e solo una chiamata. Se vuoi estendere il codice gen puoi supportare cose più complesse.
Per semplicità, il corpo lambda è limitato alla sola possibilità di accedere ai parametri lambda e può solo passarli direttamente alla funzione. È possibile rimuovere questa restrizione se si estende il codice gen nel corpo del metodo per supportare tutti i tipi di espressioni. Ci vorrebbe un po 'di lavoro però. Puoi fare tutto ciò che vuoi con il delegato che ritorna, quindi la restrizione non è un grosso problema.
È importante notare che questo codice non è perfetto. Potrebbe usare molto più validazione, e non funziona con i parametri "ref" o "out" a causa delle limitazioni dell'albero dell'espressione.
L'ho testato in casi di esempio con metodi void, metodi che restituiscono valori e metodi generici e ha funzionato. Sono sicuro, tuttavia, puoi trovare alcuni casi limite che non funzionano.
In ogni caso, ecco il codice 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;
}
fatto che si intende per una delle classi di ereditare da BasClass (dire secondClass)? –
Non del tutto; non più classi da aggiungere o modificare ... – henry000