Вы можете получить Func (или аналогичный) от объекта MethodInfo?


я понимаю, что, вообще говоря, есть последствия выполнения с помощью отражения. (На самом деле я вообще не поклонник рефлексии, это чисто академический вопрос.)

предположим, что существует некоторый класс, который выглядит так:

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

потерпи меня здесь. Я знаю, что если у меня есть экземпляр MyClass под названием x, Я могу назвать x.GetName(). Кроме того, я мог бы установить Func<string> переменная x.GetName.

теперь вот мой вопрос. Допустим, я не знайте, что выше класс называется MyClass; у меня есть какой-то объект, x, но я понятия не имею, что это такое. Я мог бы проверить, есть ли у этого объекта GetName метод сделать это:

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

предположим getName не null. Тогда я не мог, кроме того, проверить, если getName.ReturnType == typeof(string) и getName.GetParameters().Length == 0, и в этот момент я не был бы совершенно уверен, что метод, представленный моим может наверняка быть приведенным к a Func<string>, почему?

я понимаю, что есть MethodInfo.Invoke и я также понимаю, что я всегда могла!--28-->создать a Func<string> как:

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

я думаю, что я спрашиваю, есть ли какой-нибудь способ пойти С a MethodInfo объект до фактический метод, который он представляет, неся затраты на производительность отражения в

6 52

6 ответов:

этот вид заменяет мой предыдущий ответ, потому что это, хотя это немного более длинный маршрут - дает вам быстрый вызов метода и, в отличие от некоторых других ответов, позволяет вам проходить через разные экземпляры (в случае, если вы столкнетесь с несколькими экземплярами одного и того же типа). Если вы этого не хотите, проверьте мое обновление внизу (или посмотрите на ответ Бена м).

вот тестовый метод, который делает то, что вы хотите:

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

как только вы создадите делегат один раз - вы можете кэшировать его в словарь:

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

все, что вам нужно сделать, это добавить его в словарь, используя тип входящего объекта (от GetType()) в качестве ключа. В будущем вы сначала проверяете, есть ли у вас готовый делегат в словаре (и вызываете его, если да), иначе вы сначала создаете его, добавляете его, а затем вызываете его.

кстати, это очень упрощенная версия того, что DLR делает для своего динамического механизма отправки (в терминах C# это когда вы используете ключевое слово 'dynamic').

и наконец

если, как несколько человек упомянули, Вы просто хотите испечь функцию, связанную непосредственно с объектом, который вы получаете, то вы делаете это:

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

обратите внимание, однако, что как только дерево выражений будет выброшено, вам нужно убедиться, что o остается в области, в противном случае вы можете получить некоторые неприятные результаты. Самым простым способом было бы держаться за местный ссылка (в экземпляре класса, возможно) на время жизни вашего делегата. (удалено в результате комментариев Бена м)

Да, это возможно:

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

вот мой ответ, построив дерево выражения. В отличие от других ответов, результат (getNameFunc) - это функция, которая привязана к исходному экземпляру - без необходимости передавать его в качестве параметра.

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

самый простой способ сделать это через Delegate.CreateDelegate:

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

обратите внимание, что это связывает getNameFunc до x, так что для каждого x вам нужно создать новый экземпляр делегата. Этот вариант намного проще, чем Expressionпримеры. Однако с примерами на основе выражений можно создать Func<MyClass, string> getNameFuncForAny один раз, который вы можете использовать для каждого экземпляра MyClass.

чтобы создать такой getNameFuncForAny, вам понадобится метод как

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

который вы можете использовать так:

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

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

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

если вы не хотите быть привязанным к Func<MyClass, string>, можно определить

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(method.ReflectedType, "it");
    return Expression.Lambda<TDelegate>(
        Expression.Call(x, method), x).Compile();
}

вы можете построить дерево выражений, представляющее лямбда-вызов этого метода, а затем Compile() Это так, что дальнейшие вызовы будут так же быстро, как стандартные скомпилированные вызовы.

кроме того, я писал этот метод некоторое время назад на основе большой статьи MSDN, которая генерирует оболочку с помощью IL для вызова любого MethodInfo намного быстрее, чем с MethodInfo.DynamicInvoke так как после того, как код генерируется, почти нет накладных расходов по сравнению с обычным вызовом.

один из лучших подходов к моей голове - использовать динамический. Тогда вы могли бы что-то вроде этого:

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}