Pregunta

El siguiente código F# falla porque Type.DefaultBinder no quiere unirse al método Id genérico. ¿Hay un Binder alternativa que hacer esto?

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

Aquí es equivalente 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"}
        );
    }
}
¿Fue útil?

Solución

La nota bajo "Observaciones" en el página InvokeMember indica InvokeMember que no se puede utilizar para invocar un método genérico. Es de suponer que esta relacionado con el hecho de que no se puede utilizar ya sea typeof<Foo>.GetMethod("Id").Invoke(...), ya que se necesita para especificar un parámetro genérico de alguna manera.

Por otro lado, parece que es probable que pueda hackear algo juntos que tiene una oportunidad de trabajo:

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

Esto sólo se ocupa no sobrecargado métodos genéricos con un solo argumento, pero se podría intentar hacerlo más robusto. No me sorprendería que esta implementación de BindToMethod rompe todo tipo de invariantes que se espera, sin embargo, ya que devuelve un método que no fue transferido como candidato.

Otros consejos

Esto no responde directamente a su pregunta, pero si su objetivo final es sólo para llamar al método, se puede hacer por ejemplo.

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    

Aquí es una solución específica de C # para la búsqueda de un método de extensión genérica y podría ser modificado para implementar un aglutinante. FYI: mis necesidades, donde simple y no el rendimiento atado, sólo necesitaba una solución de trabajo por lo que, en ese sentido, soy consciente de que esto debe de sintonía importante y puede tener lagunas. Cualquier comentario es bienvenido sin embargo.

Espero que esto ayude con su problema

    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;
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top