I'm attempting to weave a method call using Fody to fix an issue that causes some incompatibility between ReactiveUI and PropertyChanged.Fody
Now the implementation works just fine for all of my test cases except one - weaving it into an open generic type.
Here are the important bits:
//get the method to call
var reactiveExRaiseMethod = FindTypeDefinition("ReactiveUI.IReactiveObjectExtensions").GetMethods().Single(x => x.Name == "RaisePropertyChanged");
var reactiveExRaiseMethodRef = ModuleDefinition.ImportReference(reactiveExRaiseMethod);
var raiseMethod = reactiveExRaiseMethodRef.MakeGenericMethod(type);
var il = method.Body.GetILProcessor();
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, raiseMethod);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
MakeGenericMethod
(not sure if this is correct - found on another post along the way)
public static MethodReference MakeGenericMethod(this MethodReference self, params TypeReference[] arguments)
{
if (self.GenericParameters.Count != arguments.Length)
throw new ArgumentException();
var instance = new GenericInstanceMethod(self);
foreach (var argument in arguments)
instance.GenericArguments.Add(argument);
return instance;
}
public class ReactiveObjectPropertyChangeFix : BaseModuleWeaver
{
public override void Execute()
{
foreach (var type in ModuleDefinition.Types)
{
VisitType(type); //this subsequently passes this reference to the code shown above
}
Running this it works fine for everything except the open generic type
When i look at the generated code in ILSpy i see that it generates this:
public class MyReactiveObject<T> : ReactiveObject
{
protected void OnPropertyChanged(string propertyName)
{
((MyReactiveObject<>)this).RaisePropertyChanged(propertyName);
}
}
Instead of this (taken from the passing testcase object):
public class MyReactiveObject : ReactiveObject
{
protected void OnPropertyChanged(string propertyName)
{
this.RaisePropertyChanged(propertyName);
}
}
I know that there is something wrong with the reference created by MakeGenericReference()
but I can't work out what it is, the error upon running the code on a closed generic instance (e.g. MyReactiveObject<string>
) is:
System.TypeLoadException : Could not load type 'Weavers.Tests.MyReactiveObject`1' from assembly 'Weavers.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Solved it - an open generic type like this needs to be "closed" using the open generic type parameters. (Which makes complete sense when you think about it)
public static MethodReference MakeGenericMethod(this MethodReference self, params TypeReference[] arguments)
{
if (self.GenericParameters.Count != arguments.Length)
throw new ArgumentException();
var instance = new GenericInstanceMethod(self);
foreach (var argument in arguments.Select(MakeTypeReference).ToArray())
instance.GenericArguments.Add(argument);
return instance;
TypeReference MakeTypeReference(TypeReference t)
{
if (t.HasGenericParameters && !t.IsGenericInstance)
{
//this is the magic here - if we are passed an open generic then "close" it with the it's own type parameters
return t.MakeGenericType(t.GenericParameters.Select(x => (TypeReference)x).ToArray());
}
return t;
}
}