Существует ли System.Reflection.Binder (.NET), который привязывается к универсальным методам?
-
21-09-2019 - |
Вопрос
Следующее F#
код не работает, потому что Type.DefaultBinder
не хочет привязываться к общему Id
метод.Есть ли альтернатива Binder
что бы сделать это?
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" |]
)
Вот эквивалент 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"}
);
}
}
Решение
Примечание в разделе «Примечания» Страница InvokeMember указывает, что 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 нарушит все виды ожидаемых инвариантов, поскольку она возвращает метод, который не был передан в качестве кандидата.
Другие советы
Это не дает прямого ответа на ваш вопрос, но если ваша конечная цель — просто вызвать метод, вы можете сделать, например.
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
Вот конкретное решение 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;
}