2009-09-04 29 views
2

以下F#代碼失敗,因爲Type.DefaultBinder不想綁定到通用Id方法。是否有替代Binder這樣做?是否存在綁定到泛型方法的System.Reflection.Binder(.NET)?

​​

這裏是等價的C#:

using System; 
using System.Reflection; 

public class Foo { 

    T Id<T>(T x) { 
     return x; 
    } 

    static void Main() { 
     typeof(Foo).InvokeMember 
     (
     "F", 
     BindingFlags.InvokeMethod, 
     Type.DefaultBinder, 
     (new Foo()), 
     new object[] {"test"} 
     ); 
    } 
} 
+0

可以請你刪除C#標籤 – 2009-09-04 16:52:15

+2

肯定。你能解釋爲什麼嗎?我是新來的Stackoverflow,並不知道所有的規則,但我認爲這個問題並不是真正的語言特定 - 所以如果F#標籤有,爲什麼不C#呢? – t0yv0 2009-09-04 20:34:48

回答

0

該說明下的InvokeMember page「備註」表示InvokeMember不能被用來調用的通用方法。大概這與你不能使用typeof<Foo>.GetMethod("Id").Invoke(...)的事實有關,因爲你需要以某種方式指定一個通用參數。

在另一方面,它看起來像你也許可以破解的東西一起有在工作一拍:

type MyBinder() = 
    inherit System.Reflection.Binder() with 
    let bnd = System.Type.DefaultBinder 
    override x.SelectProperty(a,b,c,d,e) = bnd.SelectProperty(a,b,c,d,e) 
    override x.ChangeType(a,b,c) = bnd.ChangeType(a,b,c) 
    override x.BindToField(a,b,c,d) = bnd.BindToField(a,b,c,d) 
    override x.ReorderArgumentArray(a,b) = bnd.ReorderArgumentArray(&a,b) 
    override x.SelectMethod(a,b,c,d) = bnd.SelectMethod(a,b,c,d) 
    override x.BindToMethod(a,meths,args,b,c,d,e) = 
    try 
     bnd.BindToMethod(a,meths,&args,b,c,d,&e) 
    with _ -> 
     let [| meth |],[| arg |] = meths,args 
     upcast (meth :?> System.Reflection.MethodInfo).MakeGenericMethod([| arg.GetType() |]) 

該處理與一個參數唯一的非重載泛型方法,但你可以嘗試使其更強大。但是,如果這個BindToMethod的實現打破了所有預期的不變量,我不會感到驚訝,因爲它返回了一個沒有作爲候選者傳入的方法。

+0

是的 - 進一步考慮,活頁夾可以對參數類型進行統一以確定方法的通用參數。但是,這可能最終會與編譯器使用的默認方法解析不兼容。我認爲,由於C#/ F#編譯器在編譯時執行此操作,可能有一個兼容的Binder - 但看起來不像。 – t0yv0 2009-09-04 20:37:53

1

這並不直接回答你的問題,但如果你的最終目標只是調用方法,你可以做例如

open System 
open System.Reflection 

type Foo() = 
    member this.Id<'T>(x: 'T) : 'T = x // ' 

let ms = typeof<Foo>.GetMethods() 
     |> Array.filter (fun m -> m.Name="Id" && m.GetGenericArguments().Length=1) 
assert(ms.Length = 1) 
let m = ms.[0] 
let r = m.MakeGenericMethod([|typeof<string>|]).Invoke(new Foo(),[|box "test"|]) 
printfn "%A" r  
0

這是一個特定的C#解決方案,用於查找通用擴展方法,並且可以修改它以實現活頁夾。僅供參考:我的需求在簡單和不受性能限制的情況下,我只需要一個工作解決方案,因此,我知道這需要進行重大調整,並且可能存在差距。儘管歡迎任何反饋。

我希望這可以幫助您解決問題

private MethodInfo FindExtensionMethod(Type instancetype, string methodName, Expression[] args) 
    { 
     Type[] parametertypes = Enumerable.Repeat(instancetype, 1).Concat(args.Cast<ConstantExpression>().Select(a => a.Value.GetType())).ToArray(); 
     var methods = AppDomain.CurrentDomain.GetAssemblies() 
      .SelectMany(a => a.GetTypes().Where(t => t.IsSealed && !t.IsGenericType && !t.IsNested)) 
      .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public) 
       .Where(m => m.IsDefined(typeof(ExtensionAttribute), false) 
        && m.Name == methodName 
        && CanBeInvokedWith(m, parametertypes)) 
       .Select(m => EnsureInvokableMethodFor(m, parametertypes))) 
      .ToList(); 

     return methods.FirstOrDefault(); 
    } 

    private MethodInfo EnsureInvokableMethodFor(MethodInfo method, Type[] parameterTypes) 
    { 
     if (method.ContainsGenericParameters) 
     { 
      var genericparams = GetGenericParametersFor(method, parameterTypes).ToArray(); 
      MethodInfo nongenric = method.MakeGenericMethod(genericparams); 
      return nongenric; 
     } 
     else 
      return method; 
    } 

    private IEnumerable<Type> GetGenericParametersFor(MethodInfo method, Type[] parameterTypes) 
    { 
     IDictionary<int, Type> args = new Dictionary<int, Type>(); 
     List<Type> genargs = new List<Type>(method.GetGenericArguments()); 
     int i = 0; 
     foreach (var parameter in method.GetParameters()) 
     { 
      if (parameter.ParameterType.IsGenericParameter) 
      { 
       AddGenArgs(args, 
        genargs.IndexOf(parameter.ParameterType), 
        parameterTypes[i]); 
      } 
      else 
      { 
       if (parameter.ParameterType.IsGenericType) 
       { 
        int j = 0; 
        foreach (Type genarg in parameter.ParameterType.GetGenericArguments()) 
        { 
         if (genarg.IsGenericParameter) 
         { 
          AddGenArgs(args, 
           genargs.IndexOf(genarg), 
           parameterTypes[i].GetGenericArguments()[j]); 
         } 
         j++; 
        } 
       } 
      } 
      i++; 
     } 

     return args.Values; 
    } 

    private static void AddGenArgs(IDictionary<int, Type> args, int argindex, Type arg) 
    { 
     if (args.ContainsKey(argindex)) 
     { 
      if (args[argindex] != arg) 
       throw new ArgumentOutOfRangeException(); 
     } 
     else 
      args[argindex] = arg; 
    } 

    private bool CanBeInvokedWith(MethodInfo method, Type[] parametertypes) 
    { 
     var parameters = method.GetParameters(); 
     if (parameters.Length != parametertypes.Length) 
      return false; 
     int i = 0; 
     return parameters.All(p => CanBeAssignedFrom(p.ParameterType, parametertypes[i++])); 
    } 

    private bool CanBeAssignedFrom(Type paramType, Type argType) 
    { 
     if (paramType.IsGenericType) 
     { 
      if (argType.IsGenericType) 
      { 
       if (paramType.GetGenericTypeDefinition() == argType.GetGenericTypeDefinition()) 
       { 
        return GenericArgsAreCompatible(
         paramType.GetGenericArguments(), 
         argType.GetGenericArguments()); 

       } 
       else 
        return false; 
      } 
      else 
       return false; 
     } 
     else 
     { 
      if (paramType.IsGenericParameter) 
       return true; 
      else 
       return paramType.IsAssignableFrom(argType); 
     } 
    } 

    private bool GenericArgsAreCompatible(Type[] paramArgs, Type[] argArgs) 
    { 
     if (paramArgs.Length != argArgs.Length) 
      return false; 

     int i = 0; 
     return paramArgs.All(p => TypesAreCompatible(p, argArgs[i++])); 
    } 

    private bool TypesAreCompatible(Type paramArg, Type argArg) 
    { 
     if (paramArg.IsGenericParameter) 
      return true; 
     else 
      return paramArg == argArg; 
    }