Can you get a Func<T> (or similar) from a MethodInfo object?
Asked Answered
E

6

69

I realize that, generally speaking, there are performance implications of using reflection. (I myself am not a fan of reflection at all, actually; this is a purely academic question.)

Suppose there exists some class that looks like this:

public class MyClass {
    public string GetName() {
        return "My Name";
    }
}

Bear with me here. I know that if I have an instance of MyClass called x, I can call x.GetName(). Furthermore, I could set a Func<string> variable to x.GetName.

Now here's my question. Let's say I don't know the above class is called MyClass; I've got some object, x, but I have no idea what it is. I could check to see if that object has a GetName method by doing this:

MethodInfo getName = x.GetType().GetMethod("GetName");

Suppose getName is not null. Then couldn't I furthermore check if getName.ReturnType == typeof(string) and getName.GetParameters().Length == 0, and at this point, wouldn't I be quite certain that the method represented by my getName object could definitely be cast to a Func<string>, somehow?

I realize there's a MethodInfo.Invoke, and I also realize I could always create a Func<string> like:

Func<string> getNameFunc = () => getName.Invoke(x, null);

I guess what I'm asking is if there's any way to go from a MethodInfo object to the actual method it represents, incurring the performance cost of reflection in the process, but after that point being able to call the method directly (via, e.g., a Func<string> or something similar) without a performance penalty.

What I'm envisioning might look something like this:

// obviously this would throw an exception if GetActualInstanceMethod returned
// something that couldn't be cast to a Func<string>
Func<string> getNameFunc = (Func<string>)getName.GetActualInstanceMethod(x);

(I realize that doesn't exist; I'm wondering if there's anything like it.)

Epilimnion answered 29/5, 2010 at 0:6 Comment(1)
On the comments you make in your edit - you will see a huge speed increase with solutions like this, because there's very little difference between a dynamically compiled delegate and a statically compiled one; once the overhead of the compilation is factored out. Since I 'discovered' the expression trees stuff, I've been using them all over the place, and would probably put it as my #1 feature from .Net 3.5 . In v4 it's even better as you can write multi-statement code - needed because of the extensions required by the DLR.Lilian
L
44

This kind of replaces my previous answer because this, although it's a slightly longer route - gives you a quick method call and, unlike some of the other answers, allows you to pass through different instances (in case you're going to encounter multiple instances of the same type). IF you don't want that, check my update at the bottom (or look at Ben M's answer).

Here's a test method that does what you want:

public class TestType
{
  public string GetName() { return "hello world!"; }
}

[TestMethod]
public void TestMethod2()
{
  object o = new TestType();

  var input = Expression.Parameter(typeof(object), "input");
  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
  //you should check for null *and* make sure the return type is string here.
  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //now build a dynamic bit of code that does this:
  //(object o) => ((TestType)o).GetName();
  Func<object, string> result = Expression.Lambda<Func<object, string>>(
    Expression.Call(Expression.Convert(input, o.GetType()), method), input).Compile();

  string str = result(o);
  Assert.AreEqual("hello world!", str);
}

Once you build the delegate once - you can cache it in a Dictionary:

Dictionary<Type, Func<object, string>> _methods;

All you do then is add it to the dictionary, using the incoming object's Type (from GetType()) as the key. In the future, you first check to see if you have a ready-baked delegate in the dictionary (and invoke it if so), otherwise you build it first, add it in, and then invoke it.

Incidentally, this is a very highly simplified version of the kind of thing that the DLR does for it's dynamic dispatch mechanism (in C# terms, that's when you use the 'dynamic' keyword).

And finally

If, as a few people have mentioned, you simply want to bake a Func bound directly to the object you receive then you do this:

[TestMethod]
public void TestMethod3()
{
  object o = new TestType();

  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //this time, we bake Expression.Constant(o) in.
  Func<string> result = Expression.Lambda<Func<string>>(
   Expression.Call(Expression.Constant(o), method)).Compile();

  string str = result(); //no parameter this time.
  Assert.AreEqual("hello world!", str);
}

Note, though, that once the expression tree gets thrown away, you need to make sure that the o stays in scope, otherwise you could get some nasty results. The easiest way would be to hold on to a local reference (in a class instance, perhaps) for the lifetime of your delegate. (Removed as a result of Ben M's comments)

Lilian answered 29/5, 2010 at 0:11 Comment(10)
Great minds think alike. :-) One question, though - why the cast expression?Twelve
Oh, but hold on - he still has to pass in a parameter to your lambda expression. I took from the original question that the generated function should be bound to the instance without having to pass a parameter.Twelve
You need the cast expression because we're defining the input parameter of the expression as 'object' and if you try to invoke the methodinfo via a reference to object it'll blow with: System.ArgumentException: Method 'System.String GetName()' declared on type 'TestProject1.UnitTest1+TestType' cannot be called with instance of type 'System.Object'Lilian
have added example of how to generate a delegate bound to the instance so that no parameters need to be passed.Lilian
One note to your revision: result is in fact a delegate targeting an instance of System.Runtime.CompilerServices.ExecutionScope, whose Globals property contains a reference to the original object (o) -- so there's no need to keep a second reference to avoid the garbage collector.Twelve
That's a comforting thing to know for some of the other code I've written that does this kind of thing (although I must confess - I've never bothered managing the lifetime of the instances... at least I know now I didn't need to!)Lilian
I was torn between accepting this or Ben M's answer since both were extremely helpful. In the end, it looks like you posted this one just a bit earlier, so I gave it to you. Thanks!Epilimnion
@Dan - it's a tough choice when you ask the question and are presented with two good answers! Thanks - glad to be of help.Lilian
If you want to cache static copies of the resulting constructed Func, instead of using a Dictionary you can follow the pattern of using a nested generic static class as in Marc Gravell's answer to another question.Reckless
you can - unless the code you're writing needs to invoke based on the result of GetType(). If I need to support both, I use a dictionary - but I agree, if you can guarantee compile-time type safety the nested generic works beautifully.Lilian
K
22

Yes, that's possible:

Func<string> func = (Func<string>)
                     Delegate.CreateDelegate(typeof(Func<string>), getName);
Kirkpatrick answered 29/5, 2010 at 0:12 Comment(4)
This answer is not instance sensitiveTwelve
@Ben M - Exactly what I was thinking - and why I decided to abandon the Delegate.CreateDelegate operation - because there'll be no way to pass the correct parameter type for the first parameter either.Lilian
Fair points. However, I don't think your solution is necessarily better. If you end up calling your dynamically compiled one on many different instances, the large cost of compilation for every instance may very well be more expensive than simply invoking the MethodInfo. If so, performance-wise you're better off making GetName be static and that takes a reference to the instance (so an explicit this pointer) resulting in a Func<Foo, string. Basically: it depends, YMMV.Kirkpatrick
Which is why my solution also includes one which allows you to pass in the instance because, obviously, closing a dynamic method over a particular instance is only ever going to hurt performance. However, I didn't recommend this to the OP as it's up to him to make that choice.Lilian
T
17

Here's my answer, by building an expression tree. Unlike the other answers, the result (getNameFunc) is a function that is bound to the original instance - without having to pass it in as a parameter.

class Program
{
    static void Main(string[] args)
    {
        var p = new Program();
        var getNameFunc = GetStringReturningFunc(p, "GetName");
        var name = getNameFunc();
        Debug.Assert(name == p.GetName());
    }

    public string GetName()
    {
        return "Bob";
    }

    static Func<string> GetStringReturningFunc(object x, string methodName)
    {
        var methodInfo = x.GetType().GetMethod(methodName);

        if (methodInfo == null ||
            methodInfo.ReturnType != typeof(string) ||
            methodInfo.GetParameters().Length != 0)
        {
            throw new ArgumentException();
        }

        var xRef = Expression.Constant(x);
        var callRef = Expression.Call(xRef, methodInfo);
        var lambda = (Expression<Func<string>>)Expression.Lambda(callRef);

        return lambda.Compile();
    }
}
Twelve answered 29/5, 2010 at 0:27 Comment(2)
+1 - a console app, not a test proj... better copy/paste value there!Lilian
Awesome answer, taught me a lot (hadn't done much work with expression trees previously). I gave it to Andras since his answer came a bit earlier, but this was really helpful too. Thanks!Epilimnion
B
11

The easiest way to do this is through Delegate.CreateDelegate:

Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate(
                                           typeof(Func<string>), x, getName);

Note that this binds getNameFunc to x, so for each x you'd need to create a new delegate instance. This option is a lot less complicated than the Expression-based examples. However, with the Expression-based examples, it's possible to create a Func<MyClass, string> getNameFuncForAny once, which you can reuse for each instance of a MyClass.

To create such a getNameFuncForAny, you'd need a method like

public Func<MyClass, string> GetInstanceMethod(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(typeof(MyClass), "it");
    return Expression.Lambda<Func<MyClass, string>>(
        Expression.Call(x, method), x).Compile();
}

which you can use like so:

Func<MyClass, string> getNameFuncForAny = GetInstanceMethod(getName);

MyClass x1 = new MyClass();
MyClass x2 = new MyClass();

string result1 = getNameFuncForAny(x1);
string result2 = getNameFuncForAny(x2);

If you don't want to be tied to Func<MyClass, string>, you can define

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(method.ReflectedType, "it");
    return Expression.Lambda<TDelegate>(
        Expression.Call(x, method), x).Compile();
}
Basso answered 29/5, 2010 at 1:23 Comment(1)
One could also Delegate.CreateDelegate (typeof (Func<MyClass, string>), getName) -- I believe it's called an "open delegate".Keri
C
1

You could build an Expression Tree representing a lambda calling this method and then Compile() it so that further calls will just be as fast as standard compiled calls.

Alternatively, I wrote this method a good while ago based on a great MSDN article, which generates a wrapper using IL to call any MethodInfo way faster than with MethodInfo.DynamicInvoke since once the code is generated, there are almost no overhead over a normal call.

Cephalometer answered 29/5, 2010 at 0:15 Comment(0)
I
0

One off the top of my head approach would be to use dynamic. You could then so something like this:

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}
Indecipherable answered 29/5, 2010 at 0:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.