6

I have a struct with a private method that I'd like to invoke. Since I plan to do this in a performance critical section, I'd like to cache a delegate to perform the action. The problem is I can't seem to bind to its method with Delegate.CreateDelegate. The struct in question is not my creation and is used in interaction with a third party library. The struct in question looks like this::

public struct A
{
     private int SomeMethod()
     {
        //body go here
     }
}

And the following code will fail with an "Error binding to target method".

Delegate.CreateDelegate(typeof(Func<A,int>),typeof(A).GetMethod("SomeMethod",BindingFlags.Instance | BindingFlags.NonPublic));

I know I can write an expression tree to perform the action, but it seems odd that I can't use my normal goto for these things the Delegate.CreateDelegate method.

The above code works just fine if A were a class. The issue only arises because A is a struct. MSDN documentation is incorrect for this overload of CreateDelegate as it does work on non-static methods.

Michael B
  • 7,512
  • 3
  • 31
  • 57
  • Out of curiosity, why use a struct instance method? Creating a delegate with a struct instance method will effectively require boxing the struct in order to use it, whereupon the boxed struct instance will effectively behave as a class type. Why not just use a class to begin with (even if the class contains nothing but a single field of struct type)? – supercat Feb 01 '13 at 23:47
  • This is an old question, but the struct came from a third party library. However, using the delegate does not box or copy it as struct instance methods are "by-ref". I needed to call a method on the handle they gave me. As they only called it at the end but I wanted to do it earlier (I don't really remember). – Michael B Feb 05 '13 at 15:58
  • I guess if you're planning on treating the struct method as though it was a static method which takes a `ref` parameter (as opposed to creating a bound delegate from the unbound delegate) you can avoid the boxing, though in that scenario I would think it would be clearer to use a static struct method. – supercat Feb 05 '13 at 16:15
  • Again, not my code. I needed to call a third party's private implementation detail because they were being a little too conservative. Their handles acted as a broker to a series of objects. – Michael B Feb 05 '13 at 16:18

3 Answers3

12

Interesting problem. From this bug report, it looks like this might be a bug that will be fixed in a future version of .NET: http://connect.microsoft.com/VisualStudio/feedback/details/574959/cannot-create-open-instance-delegate-for-value-types-methods-which-implement-an-interface#details

EDIT: actually, I think this bug report is regarding a different issue, so the behavior you're seeing may not actually be a bug.

From that bug report, I gleaned that there is a work-around if you specify the first argument of your delegate as being passed by reference. Below is a complete working example:

public struct A
{
    private int _Value;

    public int Value
    {
        get { return _Value; }
        set { _Value = value; }
    }

    private int SomeMethod()
    {
        return _Value;
    }
}

delegate int SomeMethodHandler(ref A instance);

class Program
{
    static void Main(string[] args)
    {
        var method = typeof(A).GetMethod("SomeMethod", BindingFlags.Instance | BindingFlags.NonPublic);

        SomeMethodHandler d = (SomeMethodHandler)Delegate.CreateDelegate(typeof(SomeMethodHandler), method);

        A instance = new A();

        instance.Value = 5;

        Console.WriteLine(d(ref instance));
    }
}

EDIT: Jon Skeet's answer here also discusses this issue.

Community
  • 1
  • 1
Dr. Wily's Apprentice
  • 10,212
  • 1
  • 25
  • 27
1

The first parameter of an unbound instance method delegate cannot be a value type. This is because of how value types must be handled when used as 'this' parameters. You can't simply pass them by value (as would occur if you were passing a value type as the first parameter of a static method) as then the method is acting on a copy and any mutation of the copy has no effect on the original.


UPDATE: As noted in another answer, value types when used as 'this' parameters are effectively passed by reference.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
0

You're using this overload of CreateDelegate:

Creates a delegate of the specified type to represent the specified static method.

SomeMethod is not a static method.

Use an overload that allows to specify a target object:

A target = new A();

Func<int> f = (Func<int>)Delegate.CreateDelegate(
    typeof(Func<int>),
    target,
    typeof(A).GetMethod(
        "SomeMethod",
        BindingFlags.Instance | BindingFlags.NonPublic));

This means you need to create a delegate for each instance of A though. You cannot reuse the same delegate for different instances.

The best solution is probably to construct a Lambda Expression using LINQ Expression Trees:

var p = Expression.Parameter(typeof(A), "arg");
var lambda = Expression.Lambda<Func<A, int>>(
    Expression.Call(
        p,
        typeof(A).GetMethod(
            "SomeMethod",
            BindingFlags.Instance | BindingFlags.NonPublic)),
    p);

Func<A, int> f = lambda.Compile();
dtb
  • 213,145
  • 36
  • 401
  • 431
  • Um. No, I want to use an "open" delegate. Meaning there is no target. The above code would be perfectly valid if A were a class. Also appears the MSDN documentation is bad: it also says this Note This method overload should be used when the delegate is not closed over its first argument, because it is somewhat faster in that case. The whole idea of using an open delegate is not to create a delegate for each potential target. – Michael B Dec 01 '10 at 16:53