2009-09-04 4 views
2

Le code F# suivant échoue car Type.DefaultBinder ne souhaite pas se lier à la méthode Id générique. Y at-il une alternative Binder qui ferait cela?Existe-t-il un System.Reflection.Binder (.NET) qui se lie aux méthodes génériques?

open System 
open System.Reflection 

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

typeof<Foo>.InvokeMember (
    "F", 
    BindingFlags.InvokeMethod, 
    Type.DefaultBinder, 
    (new Foo()), 
    [| box "test" |] 
) 

est ici équivalent 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

pouvez-vous s'il vous plaît enlever le C# tag –

+2

sûr. Pouvez-vous s'il vous plaît expliquer pourquoi? Je suis nouveau sur Stackoverflow et je ne connais pas encore toutes les règles, mais je pense que la question n'est pas vraiment spécifique à la langue - donc si la balise F # est là, pourquoi pas aussi C#? – t0yv0

Répondre

0

La note sous la rubrique "Remarques" sur le InvokeMember page indique que InvokeMember ne peut pas être utilisé pour invoquer une méthode générique. Vraisemblablement, cela est lié au fait que vous ne pouvez pas utiliser typeof<Foo>.GetMethod("Id").Invoke(...) non plus, puisque vous devez spécifier un paramètre générique en quelque sorte.

D'autre part, il semble que vous pouvez probablement pirater quelque chose ensemble qui a un coup de feu à travailler:

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() |]) 

Ce ne traite que des méthodes génériques non surchargés avec un seul argument, mais vous pouvez tenter de le rendre plus robuste. Je ne serais pas surpris si cette implémentation de BindToMethod casse toutes sortes d'invariants attendus, car elle retourne une méthode qui n'a pas été transmise comme candidate.

+0

Oui - en le poussant plus loin, le classeur pourrait effectuer l'unification sur les types d'argument pour déterminer les paramètres génériques d'une méthode. Cependant, cela finirait probablement par être incompatible avec la résolution de la méthode par défaut utilisée par les compilateurs. Je pensais que puisque les compilateurs C#/F # font cela au moment de la compilation, peut-être qu'il y a un classeur conforme - mais ça ne ressemble pas. – t0yv0

1

Cela ne répond pas directement à votre question, mais si votre but ultime est simplement d'appeler la méthode, vous pouvez le faire par exemple.

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

Voici une solution C# spécifique pour trouver une méthode d'extension générique et elle pourrait être modifiée pour implémenter un liant. FYI: mes besoins étaient simples et non liés à la performance, j'avais juste besoin d'une solution de travail donc, sur cette note, je suis conscient que cela nécessite un réglage majeur et peut avoir des lacunes. Tout commentaire est le bienvenu.

J'espère que cela aide votre problème

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; 
    } 
Questions connexes