views:

282

answers:

4

EDIT: Highlight difficulties with virtual and/or value-type methods

Can you create a delegate of an instance method without specifying the instance at creation time? In other words, can you create a "static" delegate that takes as it's first parameter the instance the method should be called on?

For example, how can I construct the following delegate using reflection?

Func<int, string> = i=>i.ToString();

I'm aware of the fact that I can use methodInfo.Invoke, but this is slower, and does not check for type-correctness until it is called.

When you have the MethodInfo of a particular static method, it is possible to construct a delegate using Delegate.CreateDelegate(delegateType, methodInfo), and all parameters of the static method remain free.

As Jon Skeet pointed out, you can simply apply the same to make an open delegate of an instance method if the method is non-virtual on a reference type. Deciding which method to call on a virtual method is tricky, so that's no so trivial, and value-types look like they don't work at all.

EDIT: For value types, CreateDelegate exhibits really weird behavior:

var func37 = (Func<CultureInfo,string>)(37.ToString);
var toStringMethod = typeof(int).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, new Type[] {typeof(CultureInfo) }, null);
var func42 = (Func<CultureInfo,string>)Delegate.CreateDelegate(typeof(Func<CultureInfo,string>), 42, toStringMethod,true);
Console.WriteLine( object.ReferenceEquals(func37.Method,func42.Method)); //true
Console.WriteLine(func37.Target);//37
Console.WriteLine(func42.Target);//42
Console.WriteLine(func37(CultureInfo.InvariantCulture));//37
Console.WriteLine(func42(CultureInfo.InvariantCulture));//-201040128... WTF?

Calling CreateDelegate with null as the target object throws a binding exception if the instance method belonged to a value type (this works for reference types).

+3  A: 

I'm not sure, but may be Open delegates can help you.

Upd: Follow this link, if first one don't works.

Alex Kofman
That link leads to a 404 page - maybe you mistyped?
Eamon Nerbonne
Link works fine for me.
Michael Stum
Weird, clicking on the link brings me to a 404 page, refreshing the page brings up the same 404 - But pressing enter in the url bar then (i.e. removing the referer) brings up the page. Some Browser/Server bug, apparently (FF3.5.1 only - chrome works fine). Anyhow, found the page now ;-)
Eamon Nerbonne
Thanks, Eamon. It's first time I see this bug, usually blogger links work fine.
Alex Kofman
A: 

The goog way maybe can be useing the "dynamic" type in .NET 4.0. However the Delegate need the instance (for non-static methods). The problems is more complex then lokks at first time because of polymorfism etc...

TcKs
+7  A: 

You've actually chosen a particularly tricky example, for two reasons:

  • ToString() is a virtual method inherited from object but overridden in Int32.
  • int is a value type, and there are weird rules with Delegate.CreateDelegate() when it comes to value types and instance methods - basically the first effective parameter becomes ref int rather than int

However, here's an example for String.ToUpper, which doesn't have either of those problems:

using System;
using System.Reflection;

class Test
{
    static void Main()
    {
        MethodInfo method = typeof(string).GetMethod
            ("ToUpper", BindingFlags.Instance | BindingFlags.Public,
             null, new Type[]{}, null);

        Func<string, string> func = (Func<string, string>)
            Delegate.CreateDelegate(typeof(Func<string, string>),
                                    null,
                                    method);

        string x = func("hello");

        Console.WriteLine(x);
    }
}

If that's good enough for you, great... if you really want int.ToString, I'll have to try a bit harder :)

EDIT: Here's an example for a value type, using a new delegate type which takes its first parameter by reference:

using System;
using System.Reflection;

public struct Foo
{
    readonly string value;

    public Foo(string value)
    {
        this.value = value;
    }

    public string DemoMethod()
    {
        return value;
    }
}

class Test
{
    delegate TResult RefFunc<TArg, TResult>(ref TArg arg);

    static void Main()
    {
        MethodInfo method = typeof(Foo).GetMethod
            ("DemoMethod", BindingFlags.Instance | BindingFlags.Public,
             null, new Type[]{}, null);
        RefFunc<Foo, string> func = (RefFunc<Foo, string>)
            Delegate.CreateDelegate(typeof(RefFunc<Foo, string>),
                                    null,
                                    method);

        Foo y = new Foo("hello");
        string x = func(ref y);

        Console.WriteLine(x);
    }
}
Jon Skeet
This is one of those cases where it becomes clear that C# still has room to grow as a functional language. Treating functions as first class citizens still is still not as seamless as we may like. Is there any way to exploit the dynamic features in C#4 to make this sort of thing any easier?
LBushkin
@LBushkin: I don't think so. In fact, dynamic typing and lambdas don't go terribly well together to start with - the compiler has to know what type to convert the lambda expression to at compile time.
Jon Skeet
I *had* been testing on int.ToString, but for actual use I suppose I could do without virtual methods - though not without structs. Anyhow, thanks for the heads-up, I had overlooked the complexity of virtual methods, and the error message isn't exactly informative...
Eamon Nerbonne
Okay, will try to get it to work for structs.
Jon Skeet
Wow, you're quick ;-). I'm not sure it's possible - I tried looking through MethodInfo.Invoke with reflector to see if it treats structs specially, but it ends up in native code (some CLR stub, no doubt).
Eamon Nerbonne
Looks like int is a special case? Code similar to your doesn't work for an int. Weird. Not that it's undoable to make workarounds for builtin types, but it's odd.
Eamon Nerbonne
@Eamon: What method were you trying to use? It would have to be a non-virtual instance method, I suspect. It all gets somewhat complex...
Jon Skeet
+2  A: 

You could use Lambdas to get a "somewhat" compiled static wrapper for your instance method.

The sample below isn't exactly blazingly fast, yet it should be significantly faster than any plain dynamic invoke.

The output

100000 iterations took 4 ms 
1000000 iterations took 18 ms 
10000000 iterations took 184 ms

The code

class Program
{

   public sealed class Test
   {
      public String Data { get; set; }
      public override string ToString()
      {
         return Data;
      }
   }

   static void Main(string[] args)
   {
      TestRun(100000);
      TestRun(1000000);
      TestRun(10000000);
   }

   private static void TestRun(int iterations)
   {
      var toString = typeof(Test).GetMethod("ToString",
                                            BindingFlags.Instance
                                            | BindingFlags.Public,
                                            null,
                                            Type.EmptyTypes,
                                            null);
      var call = GetCall<Test, String>(toString);
      var tests
         = (from i in Enumerable.Range(1, iterations)
            select new Test { Data = "..." + i }).ToList();

      var sw = Stopwatch.StartNew();
      tests.ForEach(i => call(i));
      sw.Stop();
      Console.WriteLine("{0} iterations took {1} ms", iterations, sw.ElapsedMilliseconds);
   }

   private static Func<T, M> GetCall<T, M>(MethodInfo methodInfo)
   {
      var input = Expression.Parameter(typeof(T), "input");
      MethodCallExpression member = Expression.Call(input, methodInfo);
      var lambda = Expression.Lambda<Func<T, M>>(member, input);

      return lambda.Compile();
   }
}
Robert Giesecke
That's a neat idea, thanks!
Eamon Nerbonne