Gibt es eine System.Reflection.Binder (.NET), dass bindet an generische Methoden?
-
21-09-2019 - |
Frage
Der folgende F#
Code schlägt fehl, da Type.DefaultBinder
nicht auf die allgemeine Id
Methode zu binden möchte. Gibt es eine Alternative Binder
, die dies tun würde?
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" |]
)
Hier ist ä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"}
);
}
}
Lösung
Der Hinweis unter "Bemerkungen" auf der InvokeMember zeigt dass InvokeMember kann nicht aufrufen, um eine generische Methode verwendet werden. Vermutlich im Zusammenhang dies auf die Tatsache, dass Sie nicht typeof<Foo>.GetMethod("Id").Invoke(...)
entweder verwenden können, da man irgendwie einen allgemeinen Parameter angeben muß.
Auf der anderen Seite, es sieht aus wie Sie können sich wahrscheinlich Hack etwas zusammen, die eine Chance auf Arbeit hat:
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() |])
Diese Griffe nur nicht überlasteten generische Methoden mit einem einzigen Argumente, aber man könnte versuchen, es robuster zu machen. Ich wäre nicht überrascht, wenn diese Implementierung von BindToMethod alle Arten von erwarteten Invarianten bricht, obwohl, da es eine Methode gibt, die nicht in als Kandidaten übergeben wurde.
Andere Tipps
Dies ist nicht direkt Ihre Frage zu beantworten, aber wenn Ihr Ziel ist es einfach, die Methode zu nennen, können Sie beispiel tun.
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
Hier ist eine spezifische C # Lösung für eine generische Erweiterung Methode zu finden, und es könnte geändert werden, um ein Bindemittel zu implementieren. Zur Info: meine Bedürfnisse, wo einfache und nicht die Leistung gebunden, ich habe gerade eine Arbeitslösung benötigt so, in diesem Sinne, ich bin mir bewusst, dass diese wichtige Abstimmung muss und Lücken aufweisen. Jedes Feedback ist willkommen though.
Ich hoffe, das hilft mit Ihrer Ausgabe
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;
}