Можете ли вы получить Func <T> (или подобное) из объекта MethodInfo? - программирование
Подтвердить что ты не робот

Можете ли вы получить Func <T> (или подобное) из объекта 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 не является нулевым. Тогда я не мог бы также проверить, есть ли getName.ReturnType == typeof(string) и getName.GetParameters().Length == 0, и на этом этапе я не уверен, что метод, представленный моим объектом GetName, определенно может быть отброшен в Func<string>, каким-то образом?

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

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

Я предполагаю, что я спрашиваю, есть ли способ переместить из a MethodInfo объект в фактический метод, который он представляет, в результате чего стоимость выполнения рефлексии в процессе, но после этого момента можно вызвать метод напрямую (через, например, 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);

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

4b9b3361

Ответ 1

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

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

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;

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

Самый простой способ сделать это - 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();
}

Ответ 5

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

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

Ответ 6

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

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