Можете ли вы получить 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);
(Я понимаю, что этого не существует, мне интересно, есть ли что-нибудь подобное.)
Ответы
Ответ 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();
}