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.

4
  • 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)? Commented Feb 1, 2013 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). Commented Feb 5, 2013 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. Commented Feb 5, 2013 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. Commented Feb 5, 2013 at 16:18

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Interesting; I didn't realize specifying the value type as 'ref' would allow it to bind.
It's possible to create delegate with 'in' instead of 'ref', but there won't be any defensive copy created. Value type can get modified in such case!
1

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

1 Comment

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

1 Comment

Thanks for the clarification on why specifying the this parameter as passed by reference is necessary for value types. Your explanation makes sense.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.