views:

385

answers:

2

The following F# code fails because Type.DefaultBinder does not want to bind to the generic Id method. Is there an alternative Binder that would do this?

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

Here is equivalent 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"}
        );
    }
}
A: 

The note under "Remarks" on the InvokeMember page indicates that InvokeMember cannot be used to invoke a generic method. Presumably this related to the fact that you can't use typeof<Foo>.GetMethod("Id").Invoke(...) either, since you need to specify a generic parameter somehow.

On the other hand, it looks like you can probably hack something together that has a shot at working:

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

This handles only non-overloaded generic methods with a single argument, but you could attempt to make it more robust. I wouldn't be surprised if this implementation of BindToMethod breaks all kinds of expected invariants, though, since it returns a method which was not passed in as a candidate.

kvb
Yes - taking it further, the binder could perform unification on argument types to determine the generic parameters of a method. However, this would probably end up being incompatible with the default method resolution the compilers use. I thought that since C#/F# compilers do this at compile time, maybe there is a compliant Binder around - but looks like not.
toyvo
A: 

This doesn't directly answer your question, but if your ultimate goal is just to call the method, you can do e.g.

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
Brian