Existe um sistema.Reflection.binder (.NET) que se liga a métodos genéricos?
-
21-09-2019 - |
Pergunta
A seguir F#
O código falha porque Type.DefaultBinder
não quer se ligar ao genérico Id
método. Existe uma alternativa Binder
Isso faria isso?
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" |]
)
Aqui está C#equivalente:
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"}
);
}
}
Solução
A nota em "Comentários" no Invokember Page Indica que o Invokemember não pode ser usado para invocar um método genérico. Presumivelmente este relacionado ao fato de que você não pode usar typeof<Foo>.GetMethod("Id").Invoke(...)
Ou você precisa especificar um parâmetro genérico de alguma forma.
Por outro lado, parece que você provavelmente pode invadir algo que tem uma chance de trabalhar:
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() |])
Isso lida com apenas métodos genéricos não carregados com um único argumento, mas você pode tentar torná-lo mais robusto. Eu não ficaria surpreso se essa implementação do bindtomethod quebrar todos os tipos de invariantes esperados, pois retorna um método que não foi aprovado como candidato.
Outras dicas
Isso não responde diretamente à sua pergunta, mas se seu objetivo final é apenas chamar o método, você pode fazer por exemplo
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
Aqui está uma solução C# específica para encontrar um método de extensão genérica e pode ser modificado para implementar um fichário. FYI: Minhas necessidades onde o desempenho simples e não vinculou, eu só precisava de uma solução de trabalho, para que, nessa nota, estou ciente de que isso precisa de um ajuste importante e pode ter lacunas. Qualquer feedback é bem -vindo.
Espero que isso ajude com seu 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;
}