Utilisation base
en C# ne fonctionne que pour la base immédiate. Vous ne pouvez pas accéder à un membre de base.
Il semble que quelqu'un d'autre m'a battu au punch avec la réponse à propos de ce qu'il est possible de faire en IL.
Cependant, je pense que la façon dont j'ai fait le code gen a quelques avantages, donc je vais le poster de toute façon. La chose que j'ai faite différemment est d'utiliser des arbres d'expression, ce qui vous permet d'utiliser le compilateur C# pour effectuer une résolution de surcharge et une substitution d'arguments génériques.
Ce truc est compliqué, et vous ne voulez pas avoir à le reproduire vous-même si vous pouvez l'aider. Dans votre cas, le code fonctionnerait comme ceci:
var del =
CreateNonVirtualCall<Program, BaseClass, Action<ThirdClass>>
(
x=>x.SayNo()
);
Vous auriez probablement eu envie de stocker le délégué dans un champ statique en lecture seule, de sorte que vous suffit de compiler une fois.
Vous devez spécifier 3 arguments génériques:
Le type de propriétaire - C'est la classe que vous auriez invoqué le code de si vous n'utilisez pas « CreateNonVirtualCall ».
La classe de base - C'est la classe que vous voulez faire l'appel non virtuel à partir
Un type délégué. Cela devrait représenter la signature de la méthode appelée avec un paramètre supplémentaire pour l'argument "this". Il est possible d'éliminer cela, mais cela nécessite plus de travail dans la méthode de génération de code.
La méthode prend un seul argument, un lambda représentant l'appel. Ce doit être un appel, et seulement un appel. Si vous voulez étendre le code gen, vous pouvez supporter des choses plus complexes. Pour simplifier, le corps lambda est limité à la seule possibilité d'accéder aux paramètres lambda, et ne peut les transmettre directement à la fonction. Vous pouvez supprimer cette restriction si vous étendez le code gen dans le corps de la méthode pour prendre en charge tous les types d'expression. Cela prendrait du travail cependant. Vous pouvez faire ce que vous voulez avec le délégué qui revient, donc la restriction n'est pas trop importante.
Il est important de noter que ce code n'est pas parfait. Il pourrait utiliser beaucoup plus de validation, et cela ne fonctionne pas avec les paramètres "ref" ou "out" à cause des limitations de l'arbre d'expression.
Je l'ai testé dans des exemples de cas avec des méthodes void, des méthodes renvoyant des valeurs et des méthodes génériques, et cela a fonctionné. Je suis sûr, cependant, vous pouvez trouver quelques cas de bord qui ne fonctionnent pas.
Dans tous les cas, voici le code 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;
}
avez-vous l'intention d'un de vos classes pour hériter de BasClass (disons deuxième classe)? –
Pas tout à fait; plus de classes à ajouter ou à modifier ... – henry000