RuntimeReflectionExtensions.GetRuntimeMethod does

2019-06-16 17:47发布

问题:

Does anyone have any clue on why calling GetRuntimeMethod is returning null for the following case?

_toListMethod = typeof(Enumerable).GetRuntimeMethod("ToList",  new Type[] { typeof(IEnumerable<>) });

It should work just like it does for:

_castMethod = typeof(Enumerable).GetRuntimeMethod("Cast", new Type[] { typeof(IEnumerable) });

I tried to debug this by running the following code:

var bah = typeof (Enumerable).GetRuntimeMethods().Where(m => m.Name.Contains("ToList"));
var derp = bah.First().GetParameters();

To my surprise, the first line returns a collection which contains the MethodInfo I am trying to get and the second line confirms that the expected paramter type is IEnumerable<>.

The two method signatures, Cast and ToList, are similar and I cannot see any reason why getting the MethodInfo for ToList would fail.

This code is running on a Portable Class Library with TargetFrameworkProfile set to Profile78.

Thanks!

Update: Until I have a good solution, there is one ugly workaround which works for me:

_toListMethod = typeof(Enumerable).GetRuntimeMethods().First(m => m.Name.Contains("ToList"));

回答1:

I looked up the signatures, and they look like this:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source);
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source);

I believe there might be something fishy going on with GetRunTimeMethod and extension methods with generic parameters since this does not work:

var intToListMethod = typeof(IEnumerable<int>).GetRuntimeMethod("ToList", new Type[] { typeof(IEnumerable<int>) });

I took some time and tried to create a valid extension method for your needed behaviour, and I came up with the code snippet below. It works for me.

public static class RuntimeMethodExtensions
{
 public static MethodInfo GetRuntimeMethodsExt(this Type type, string name, params Type[] types)
 {
  // Find potential methods with the correct name and the right number of parameters
  // and parameter names
  var potentials = (from ele in type.GetMethods()
                    where ele.Name.Equals(name)
                    let param = ele.GetParameters()
                    where param.Length == types.Length
                    && param.Select(p => p.ParameterType.Name).SequenceEqual(types.Select(t => t.Name))
                    select ele);

  // Maybe check if we have more than 1? Or not?
  return potentials.FirstOrDefault();
 }
}

Called as this:

var myLookup = typeof(Enumerable).GetRuntimeMethodsExt("ToList", typeof(IEnumerable<>));

Below I've attached the IL produced when compiling my third case and your two cases. None of the ToList methods produce any result.

// ToList<int>
IL_0001:  ldtoken     System.Collections.Generic.IEnumerable<System.Int32>
IL_0006:  call        System.Type.GetTypeFromHandle
IL_000B:  ldstr       "ToList"
IL_0010:  ldc.i4.1    
IL_0011:  newarr      System.Type
IL_0016:  stloc.3     // CS$0$0000
IL_0017:  ldloc.3     // CS$0$0000
IL_0018:  ldc.i4.0    
IL_0019:  ldtoken     System.Collections.Generic.IEnumerable<System.Int32>
IL_001E:  call        System.Type.GetTypeFromHandle
IL_0023:  stelem.ref  
IL_0024:  ldloc.3     // CS$0$0000
IL_0025:  call        System.Reflection.RuntimeReflectionExtensions.GetRuntimeMethod
IL_002A:  stloc.0     // _intToListMethod
// ToList<>
IL_002B:  ldtoken     System.Linq.Enumerable
IL_0030:  call        System.Type.GetTypeFromHandle
IL_0035:  ldstr       "ToList"
IL_003A:  ldc.i4.1    
IL_003B:  newarr      System.Type
IL_0040:  stloc.3     // CS$0$0000
IL_0041:  ldloc.3     // CS$0$0000
IL_0042:  ldc.i4.0    
IL_0043:  ldtoken     System.Collections.Generic.IEnumerable<>
IL_0048:  call        System.Type.GetTypeFromHandle
IL_004D:  stelem.ref  
IL_004E:  ldloc.3     // CS$0$0000
IL_004F:  call        System.Reflection.RuntimeReflectionExtensions.GetRuntimeMethod
IL_0054:  stloc.1     // _toListMethod
// Cast<>
IL_0055:  ldtoken     System.Linq.Enumerable
IL_005A:  call        System.Type.GetTypeFromHandle
IL_005F:  ldstr       "Cast"
IL_0064:  ldc.i4.1    
IL_0065:  newarr      System.Type
IL_006A:  stloc.3     // CS$0$0000
IL_006B:  ldloc.3     // CS$0$0000
IL_006C:  ldc.i4.0    
IL_006D:  ldtoken     System.Collections.Generic.IEnumerable<>
IL_0072:  call        System.Type.GetTypeFromHandle
IL_0077:  stelem.ref  
IL_0078:  ldloc.3     // CS$0$0000
IL_0079:  call        System.Reflection.RuntimeReflectionExtensions.GetRuntimeMethod
IL_007E:  stloc.2     // _castMethod


回答2:

You expect this...

typeof(Enumerable).GetRuntimeMethod("ToList", new Type[] { typeof(IEnumerable<>) });

...to return this...

public static List ToList(this IEnumerable source);

...because you assume the type of the parameter source is equal to typeof(IEnumerable<>). It is not. The type of the parameter source is IEnumerable<TSource> while typeof(IEnumerable<>) the former is an instantiation of the latter (which is the generic type definition) with the generic type parameter defined by the ToList method.

In general, it's difficult to define a simple API for binding to such methods because C# provides no simple way (non-reflection) to get a type representing a generic parameter defined on a method.