Я понимаю, что вообще говоря, существуют последствия производительности использования отражения. (Я сам не поклонник отражения вообще на самом деле; это - чисто академический вопрос.)
Предположим там существует некоторый класс, который похож на это:
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);
(Я понимаю, что это не существует; я задаюсь вопросом, существует ли что-нибудь как он.)
Этот тип заменяет мой предыдущий ответ, потому что этот, хотя и немного более длинный путь, дает вам быстрый вызов метода и, в отличие от некоторых другие ответы, позволяет вам проходить через разные экземпляры (в случае, если вы встретите несколько экземпляров одного и того же типа). ЕСЛИ вы этого не хотите, проверьте мое обновление внизу (или посмотрите ответ Бена М.).
Вот тестовый метод, который делает то, что вы хотите:
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
остается в области видимости, иначе вы можете получить неприятные результаты. Самый простой способ - сохранить локальную ссылку (возможно, в экземпляре класса) на время жизни вашего делегата.
Вот мой ответ - построение дерева выражений. В отличие от других ответов, результат ( 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();
}
}
Один из самых важных моих подходов - использовать динамический. Тогда вы могли бы сделать что-то вроде этого:
if( /* This method can be a Func<string> */)
{
dynamic methodCall = myObject;
string response = methodCall.GetName();
}
Да, это возможно:
Func<string> func = (Func<string>)
Delegate.CreateDelegate(typeof(Func<string>), getName);
Вы можете построить дерево выражений, представляющее лямбду, вызывающую этот метод, а затем Compile()
его так, что дальнейшие вызовы будут такими же быстрыми, как и стандартные скомпилированные вызовы.
В качестве альтернативы я написал этот метод некоторое время назад на основе замечательной статьи в MSDN, который генерирует обертку с использованием IL для вызова любого MethodInfo
намного быстрее, чем с помощью MethodInfo.DynamicInvoke
, поскольку после генерации кода почти нет накладных расходов по сравнению с обычным вызовом.
Проще всего это сделать через Delegate. CreateDelegate
:
Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate(
typeof(Func<string>), x, getName);
Обратите внимание, что это связывает getNameFunc
с x
, поэтому для каждого x
вам придется создавать новый экземпляр делегата. Этот вариант гораздо менее сложен, чем примеры на основе Expression
. Однако в примерах на основе Expression можно один раз создать Func
, который можно повторно использовать для каждого экземпляра 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();
}