buunguyen / fasterflect

.NET Reflection Made Fast and Simple ⛺
https://www.nuget.org/packages/fasterflect/
Apache License 2.0
282 stars 55 forks source link

Generic type parameters ignored in cache, wrong method invoked #21

Open bacar opened 2 years ago

bacar commented 2 years ago

Hi,

The following test fails:

public class GenericTest
{
    static string Foo<T>() => typeof(T).FullName;

    [Test]
    public void Generic_type_params_respected()
    {
        MethodInfo x = typeof(GenericTest).GetMethod(nameof(Foo), BindingFlags.Static|BindingFlags.NonPublic);

        var gen1 = x.MakeGenericMethod(typeof(int));
        var gen2 = x.MakeGenericMethod(typeof(double));

        Assert.AreEqual(typeof(int).FullName, gen1.Call());
        Assert.AreEqual(typeof(double).FullName, gen2.Call());
    }
}
  Expected string length 13 but was 12. Strings differ at index 7.
  Expected: "System.Double"
  But was:  "System.Int32"
  ------------------^

The second call gen2.Call() ends up invoking the first method, with the wrong (int) type parameters. The problem is reversed if you swap the two assert lines to make the double one come first.

        Assert.AreEqual(typeof(double).FullName, gen2.Call());
        Assert.AreEqual(typeof(int).FullName, gen1.Call());
  Expected string length 12 but was 13. Strings differ at index 7.
  Expected: "System.Int32"
  But was:  "System.Double"
  ------------------^

The problem appears to be in the MethodInvocationEmitter ctor - it passes null as the genericTypes argument to the CallInfo constructor, which means that the generic type arguments of the method are ignored when looking up an appropriate delegate from the BaseEmitter.cache

private MethodInvocationEmitter( Type targetType, Flags bindingFlags, string name, Type[] parameterTypes,
                                     MemberInfo methodInfo )
        : base(new CallInfo(targetType, null, bindingFlags, MemberTypes.Method, name, parameterTypes, methodInfo, true))
    {
    }
pmg23 commented 2 years ago

see https://github.com/HelloKitty/fasterflect/issues/7

Zxynine commented 2 years ago

Yeah so I found a solution, Go into MethodInvocationEmmiter class and change the ctor-

    public MethodInvocationEmitter( MethodInfo methodInfo, Flags bindingFlags )
            : this( methodInfo.DeclaringType, bindingFlags, methodInfo.Name, methodInfo.GetParameters().ToTypeArray(), methodInfo ) {
        }

into this-

        public MethodInvocationEmitter( MethodInfo methodInfo, Flags bindingFlags )
            : this( methodInfo.DeclaringType, methodInfo.GetGenericArguments(), bindingFlags, methodInfo.Name, methodInfo.GetParameters().ToTypeArray(), methodInfo ) {
        }

And add a new private ctor-

        private MethodInvocationEmitter( Type targetType, Type[] genericTypes, Flags bindingFlags, string name, Type[] parameterTypes, MemberInfo methodInfo )
            : base(new CallInfo(targetType, genericTypes, bindingFlags, MemberTypes.Method, name, parameterTypes, methodInfo, true)) {
        }

I was blown away at how easy of a solution this was.