Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Uncurrying" an instance method in .NET

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.

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).

Some follow-up years later: The incorrectly-bound target that caused func42(CultureInfo.InvariantCulture); to return "-201040128" instead of "42" in my example was memory corruption that could have allowed remote code execution (cve-2010-1898); this was fixed in 2010 in the ms10-060 security update. Current frameworks correctly print 42! That doesn't make answering this question any easier, but explains the particularly weird behavior in the example.

like image 565
Eamon Nerbonne Avatar asked Jul 31 '09 12:07

Eamon Nerbonne


2 Answers

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 :)

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);
    }
}
like image 172
Jon Skeet Avatar answered Oct 09 '22 23:10

Jon Skeet


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

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

like image 28
Alex Kofman Avatar answered Oct 10 '22 00:10

Alex Kofman