Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mono.Cecil: call GENERIC base class' method from other assembly

I'm following up on my earlier question: Mono.Cecil: call base class' method from other assembly.
I'm doing the same thing, but if my base class is generic it doesn't work.

//in Assembly A
class BaseVM<T> {}

//in Assembly B
class MyVM : Base<SomeModel> {
 [NotifyProperty]
 public string Something {get;set;}
}

It weaves the following code:

L_000e: call instance void [AssemblyA]Base`1::RaisePropertyChanged(string)

instead of

L_000e: call instance void [AssemblyA]Base`1<class SomeModel>::RaisePropertyChanged(string)

What is there to change?

like image 746
TDaver Avatar asked Feb 11 '11 12:02

TDaver


1 Answers

In your previous post you indicate that you're using code like:

TypeDefinition type = ...;
TypeDefintion baseType = type.BaseType.Resolve ();
MethodDefinition baseMethod = baseType.Methods.First (m => ...);
MethodReference baseMethodReference = type.Module.Import (baseMethod);
il.Emit (OpCodes.Call, baseMethodReference);

Obviously, this is not suitable for generics:

When you Resolve () the .BaseType, you're losing the generic instantiation information. You need to recreate the appropriate method call with the proper generic information from the base type.

To simplify things, let's use the following methods, taken from the Cecil test suite:

public static TypeReference MakeGenericType (this TypeReference self, params TypeReference [] arguments)
{
    if (self.GenericParameters.Count != arguments.Length)
        throw new ArgumentException ();

    var instance = new GenericInstanceType (self);
    foreach (var argument in arguments)
        instance.GenericArguments.Add (argument);

    return instance;
}

public static MethodReference MakeGeneric (this MethodReference self, params TypeReference [] arguments)
{
    var reference = new MethodReference(self.Name,self.ReturnType) {
        DeclaringType = self.DeclaringType.MakeGenericType (arguments),
        HasThis = self.HasThis,
        ExplicitThis = self.ExplicitThis,
        CallingConvention = self.CallingConvention,
    };

    foreach (var parameter in self.Parameters)
        reference.Parameters.Add (new ParameterDefinition (parameter.ParameterType));

    foreach (var generic_parameter in self.GenericParameters)
        reference.GenericParameters.Add (new GenericParameter (generic_parameter.Name, reference));

    return reference;
}

With those, you can rewrite your code as:

TypeDefinition type = ...;
TypeDefintion baseTypeDefinition = type.BaseType.Resolve ();
MethodDefinition baseMethodDefinition = baseTypeDefinition.Methods.First (m => ...);
MethodReference baseMethodReference = type.Module.Import (baseMethodDefinition);
if (type.BaseType.IsGenericInstance) {
    var baseTypeInstance = (GenericInstanceType) type.BaseType;
    baseMethodReference = baseMethodReference.MakeGeneric (baseTypeInstance.GenericArguments.ToArray ());
}

il.Emit (OpCodes.Call, baseMethodReference);
like image 90
Jb Evain Avatar answered Sep 28 '22 01:09

Jb Evain