Это можно сделать, но это не так!
Например, чтобы получить первую перегрузку Where
, упомянутую в вашем вопросе, вы можете сделать это:
var where1 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Или если вы хотите вторую перегрузку:
var where2 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(int)
&& x.A[2] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Этот вопрос составляет около 2-х лет, но я придумал (что я думаю) изящное решение, и подумал, что поделюсь им с хорошими людьми здесь, в StackOverflow. Надеюсь, это поможет тем, кто приезжает сюда через различные поисковые запросы.
Проблема, как заявил плакат, заключается в том, чтобы получить правильный общий метод. Например, метод расширения LINQ может иметь массу перегрузок с аргументами типа, вложенными в другие общие типы, которые все используются как параметры. Я хотел сделать что-то вроде этого:
var where = typeof(Enumerable).GetMethod(
"Where",
typeof(IQueryable<Refl.T1>),
typeof(Expression<Func<Refl.T1, bool>>
);
var group = typeof(Enumerable).GetMethod(
"GroupBy",
typeof(IQueryable<Refl.T1>),
typeof(Expression<Func<Refl.T1, Refl.T2>>
);
Как вы можете видеть, я создал несколько типов заглушек «T1» и «T2», вложенные классы в классе «Refl» (статический класс который содержит все мои различные функции расширения утилиты Reflection и т. д. Они служат в качестве заполнителей для тех параметров, которые обычно выполнялись бы. Приведенные выше примеры соответствуют следующим методам LINQ:
Enumerable.Where(IQueryable<TSource> source, Func<TSource, bool> predicate);
Enumerable.GroupBy(IQueryable<Source> source, Func<TSource, TKey> selector);
So должно быть ясно, что Refl.T1
отправляется туда, где TSource
исчезнет в обоих этих вызовах, а Refl.T2
представляет параметр TKey
. Классы TX
объявляются как таковые:
static class Refl {
public sealed class T1 { }
public sealed class T2 { }
public sealed class T3 { }
// ... more, if you so desire.
}
] С тремя классами TX
ваш код может идентифицировать методы, содержащие до трех параметров типового типа.
Следующий бит магии - реализовать функцию, выполняющую поиск через GetMethods()
:
public static MethodInfo GetMethod(this Type t, string name, params Type[] parameters)
{
foreach (var method in t.GetMethods())
{
// easiest case: the name doesn't match!
if (method.Name != name)
continue;
// set a flag here, which will eventually be false if the method isn't a match.
var correct = true;
if (method.IsGenericMethodDefinition)
{
// map the "private" Type objects which are the type parameters to
// my public "Tx" classes...
var d = new Dictionary<Type, Type>();
var args = method.GetGenericArguments();
if (args.Length >= 1)
d[typeof(T1)] = args[0];
if (args.Length >= 2)
d[typeof(T2)] = args[1];
if (args.Length >= 3)
d[typeof (T3)] = args[2];
if (args.Length > 3)
throw new NotSupportedException("Too many type parameters.");
var p = method.GetParameters();
for (var i = 0; i < p.Length; i++)
{
// Find the Refl.TX classes and replace them with the
// actual type parameters.
var pt = Substitute(parameters[i], d);
// Then it's a simple equality check on two Type instances.
if (pt != p[i].ParameterType)
{
correct = false;
break;
}
}
if (correct)
return method;
}
else
{
var p = method.GetParameters();
for (var i = 0; i < p.Length; i++)
{
var pt = parameters[i];
if (pt != p[i].ParameterType)
{
correct = false;
break;
}
}
if (correct)
return method;
}
}
return null;
}
Приведенный выше код выполняет основную часть работы - он выполняет итерацию через все методы определенного типа и сравнивает их с заданными типами параметров для поиска. Но подождите! «заменить» функцию? Это хорошая малорекурсивная функция, которая будет искать все дерево типов параметров. В конце концов, тип параметра сам может быть общим типом, который может содержать типы Refl.TX
, которые должны быть заменены для параметров «реального» типа которые скрыты от нас.
private static Type Substitute(Type t, IDictionary<Type, Type> env )
{
// We only really do something if the type
// passed in is a (constructed) generic type.
if (t.IsGenericType)
{
var targs = t.GetGenericArguments();
for(int i = 0; i < targs.Length; i++)
targs[i] = Substitute(targs[i], env); // recursive call
t = t.GetGenericTypeDefinition();
t = t.MakeGenericType(targs);
}
// see if the type is in the environment and sub if it is.
return env.ContainsKey(t) ? env[t] : t;
}
Вы можете несколько изящно выбрать определенную общую перегрузку метода во время компиляции, не передавая ни одной строки для поиска во время выполнения, как и другие ответы здесь.
Предположим, что у вас несколько статических методов с таким же именем, как:
public static void DoSomething<TModel>(TModel model)
public static void DoSomething<TViewModel, TModel>(TViewModel viewModel, TModel model)
// etc
Если вы создаете Action или Func, который соответствует общему счету и количеству параметров перегрузок, которые вы ищете, вы можете выберите его во время компиляции с относительно небольшим количеством акробатиков.
Пример: выберите первый метод - возвращает void, поэтому используйте Action, принимает один общий. Мы используем объект, чтобы еще не указывать тип:
var method = new Action<object>(MyClass.DoSomething<object>);
Пример: выберите второй метод - возвращает void, поэтому Action, 2 generic типа, поэтому дважды используйте объект типа, один раз для каждого из двух общих параметров :
var method = new Action<object, object>(MyClass.DoSomething<object, object>);
Вы получили только тот метод, который вам нужен, без сумасшедшей сантехники, и без поиска во времени или использования рискованных строк.
Обычно в Reflection вы хотите объект MethodInfo, который вы также можете получить скомпилированным способом. Это когда вы передаете фактические общие типы, которые вы хотите использовать в своем методе. Предполагая, что вам нужен второй метод выше:
var methodInfo = method.Method.MakeGenericMethod(type1, type2);
Существует ваш общий метод без какого-либо поиска отражений или вызовов GetMethod () или фальшивых строк.
Конкретный пример, который вы цитируете с помощью Queryable.Where, когда перегрузки заставляют вас немного причудливо определять Func, но обычно следуют одному и тому же шаблону. Подпись для наиболее часто используемого метода расширения Where () :
public static IQueryable<TModel> Where<TModel>(this IQueryable<TModel>, Expression<Func<TModel, bool>>)
Очевидно, что это будет немного сложнее - вот оно:
var method = new Func<IQueryable<object>,
Expression<Func<object, bool>>,
IQueryable<object>>(Queryable.Where<object>);
var methodInfo = method.Method.MakeGenericMethod(modelType);
Включение комментария Валери - чтобы получить метод экземпляра, вам нужно сделать что-то очень похожее. Предположим, что у вас есть этот метод экземпляра в вашем классе:
public void MyMethod<T1>(T1 thing)
Сначала выберите метод так же, как и для статики:
var method = new Action<object>(MyMethod<object>);
Затем вызовите GetGenericMethodDefinition()
, чтобы перейти к общий метод MethodInfo и, наконец, передать ваш тип (ы) с помощью MakeGenericMethod()
:
var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1);
Это не было задано в вопросе, но как только вы сделайте это, вы можете выбрать метод в одном месте и решить, какие типы передать его другому. Вы можете отделить эти 2 шага.
Если вы не уверены в параметрах родового типа, которые вы собираетесь пройти, вы можете всегда приобретать объект MethodInfo без них.
Static :
var methodInfo = method.Method;
Экземпляр:
var methodInfo = method.Method.GetGenericMethodDefinition();
И передайте это другому методу, который знает типы, которые он хочет создать, и вызовет метод с - например:
processCollection(methodInfo, type2);
...
protected void processCollection(MethodInfo method, Type type2)
{
var type1 = typeof(MyDataClass);
object output = method.MakeGenericMethod(type1, type2).Invoke(null, new object[] { collection });
}
. Особенно это особенно важно в том, чтобы выбрать конкретный метод экземпляра класса изнутри класса, а затем разоблачить его для внешних вызывающих абонентов, которым он нужен с различными типами позже.
Редактирование: очищенные объяснения, пример метода экземпляра Валери.
Я обнаружил самый простой способ использования выражений iQueryable при вызове метода с использованием отражения. См. Ниже код:
Вы можете использовать выражение IQueryable в соответствии с требованием.
var attributeName = "CarName";
var attributeValue = "Honda Accord";
carList.FirstOrDefault(e => e.GetType().GetProperty(attributeName).GetValue(e, null) as string== attributeValue);
Использовать DynamicMethods.GenericMethodInvokerMethod , GetMethod недостаточно для использования с generics
Ответ Криса Москини хорош, когда вы знаете имя метода во время компиляции. Ответ Antamir работает, если мы получим имя метода во время выполнения, но довольно перебор.
Я использую другой способ, для которого я получил вдохновение с использованием отражателя из функции .NET Expression.Call
, которая выбирает правильный общий метод из строки.
public static MethodInfo GetGenericMethod(Type declaringType, string methodName, Type[] typeArgs, params Type[] argTypes) {
foreach (var m in from m in declaringType.GetMethods()
where m.Name == methodName
&& typeArgs.Length == m.GetGenericArguments().Length
&& argTypes.Length == m.GetParameters().Length
select m.MakeGenericMethod(typeArgs)) {
if (m.GetParameters().Select((p, i) => p.ParameterType == argTypes[i]).All(x => x == true))
return m;
}
return null;
}
var m = ReflectionUtils.GetGenericMethod(typeof(Queryable), "Where", new[] { typeof(Person) }, typeof(IQueryable<Person>), typeof(Expression<Func<Person, bool>>));
Если вам нужно только определение общего метода или просто не знаю тип T
в то время, вы можете использовать некоторые фиктивные типы, а затем разделите информацию родословной:
var m = ReflectionUtils.GetGenericMethod(typeof(Queryable), "Where", new[] { typeof(object) }, typeof(IQueryable<object>), typeof(Expression<Func<object, bool>>));
m = m.GetGenericMethodDefinition();
В дополнение к ответу @ MBoros.
Вы можете избежать написания сложных общих аргументов с помощью этого вспомогательного метода:
public static MethodInfo GetMethodByExpression<Tin, Tout>(Expression<Func<IQueryable<Tin>, IQueryable<Tout>>> expr)
{
return (expr.Body as MethodCallExpression).Method;
}
Использование:
var where = GetMethodByExpression<int, int>(q => q.Where((x, idx) => x > 2));
Или
var select = GetMethodByExpression<Person, string>(q => q.Select(x => x.Name));
Пусть компилятор сделает это за вас:
var fakeExp = (Expression<Func<IQueryable<int>, IQueryable<int>>>)(q => q.Where((x, idx) => x> 2));
var mi = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition();
для индекса Where с индексом или просто оставьте второй параметр в выражении Where для объекта без
Ответ Antamir был очень полезен для меня, но у него есть ошибка в том, что он не подтверждает, что количество параметров в найденном методе соответствует количеству типов, переданных при составлении сочетания общих и конкретных типов.
Например, если вы запустили:
type.GetMethod("MyMethod",typeof(Refl.T1),typeof(bool))
, он не может различать два метода:
MyMethod<T>(T arg1)
MyMethod<T>(T arg1, bool arg2)
Два вызова:
var p = method.GetParameters();
следует изменить на:
var p = method.GetParameters();
if (p.Length != parameters.Length)
{
correct = false;
continue;
}
Кроме того, обе существующие строки «break» должны быть «продолжены».
Я сделал небольшую вспомогательную функцию:
Func<Type, string, Type[], Type[], MethodInfo> getMethod = (t, n, genargs, args) =>
{
var methods =
from m in t.GetMethods()
where m.Name == n && m.GetGenericArguments().Length == genargs.Length
let mg = m.IsGenericMethodDefinition ? m.MakeGenericMethod(genargs) : m
where mg.GetParameters().Select(p => p.ParameterType).SequenceEqual(args)
select mg
;
return methods.Single();
};
Работает для простых не-генериков:
var m_movenext = getMethod(typeof(IEnumerator), nameof(IEnumerator.MoveNext), Type.EmptyTypes, Type.EmptyTypes);
Как и для сложных дженериков:
var t_source = typeof(fillin1);
var t_target = typeof(fillin2);
var m_SelectMany = getMethod(
typeof(Enumerable),
nameof(Enumerable.SelectMany),
new[] {
t_source,
t_target
},
new[] {
typeof(IEnumerable<>).MakeGenericType(t_source),
typeof(Func<,>).MakeGenericType(t_source, typeof(IEnumerable<>).MakeGenericType(t_target))
});
Другое решение, которое может показаться вам полезным - можно получить MethodInfo
на основе Expression.Call
, который уже имеет логику для разрешения перегрузки.
Например, если вам нужно получить некоторый конкретный метод Enumerable.Where
, который может быть выполнен с использованием следующего кода:
var mi = Expression.Call(typeof (Enumerable), "Where", new Type[] {typeof (int)},
Expression.Default(typeof (IEnumerable<int>)), Expression.Default(typeof (Func<int, int, bool>))).Method;
Третий аргумент в примере - описывает типы общие аргументы и все остальные аргументы - типы параметров.
Таким же образом можно получить даже нестатические общие методы объекта. Вам нужно изменить только первый аргумент от typeof (YourClass)
до Expression.Default(typeof (YourClass))
.
На самом деле, я использовал этот подход в моем плагине для .NET Reflection API. Вы можете проверить, как это работает здесь