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

只谈情不闲聊 提交于 2019-11-27 11:01:01

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)

Yes, that's possible:

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

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

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

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.

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();
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!