Можно ли получить Func <T> (или подобный) от объекта MethodInfo?

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

Предположим там существует некоторый класс, который похож на это:

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

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

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

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

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

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

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

Я предполагаю то, что я спрашиваю, то, если существует какой-либо способ пойти от a MethodInfo возразите против фактического метода, который это представляет, подвергаясь стоимости производительности отражения в процессе, но после той способности точки назвать метод непосредственно (через, например, a Func<string> или что-то подобное) без потери производительности.

То, что я предполагаю, могло бы выглядеть примерно так:

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

(Я понимаю, что это не существует; я задаюсь вопросом, существует ли что-нибудь как он.)

57
задан BartoszKP 30 October 2016 в 21:36
поделиться

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

И, наконец,

Если, как упомянули несколько человек, вы просто хотите запечь Func, привязанный непосредственно к полученному объекту, вы делаете следующее:

[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 остается в области видимости, иначе вы можете получить неприятные результаты. Самый простой способ - сохранить локальную ссылку (возможно, в экземпляре класса) на время жизни вашего делегата. (Удалено в результате комментариев Бена М.)

38
ответ дан 24 November 2019 в 19:43
поделиться

Вот мой ответ - построение дерева выражений. В отличие от других ответов, результат ( 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();
    }
}
14
ответ дан 24 November 2019 в 19:43
поделиться

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

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}
0
ответ дан 24 November 2019 в 19:43
поделиться

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

Func<string> func = (Func<string>)
                     Delegate.CreateDelegate(typeof(Func<string>), getName);
16
ответ дан 24 November 2019 в 19:43
поделиться

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

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

1
ответ дан 24 November 2019 в 19:43
поделиться

Проще всего это сделать через Delegate. CreateDelegate:

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

Обратите внимание, что это связывает getNameFunc с x, поэтому для каждого x вам придется создавать новый экземпляр делегата. Этот вариант гораздо менее сложен, чем примеры на основе Expression. Однако в примерах на основе Expression можно один раз создать Func 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, вы можете определить

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(method.ReflectedType, "it");
    return Expression.Lambda<TDelegate>(
        Expression.Call(x, method), x).Compile();
}
9
ответ дан 24 November 2019 в 19:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: