Получить правильный метод путем отражения, когда параметры отличаются только в родовом типе [дубликат]

30
задан jnm2 22 February 2016 в 14:07
поделиться

11 ответов

Это можно сделать, но это не так!

Например, чтобы получить первую перегрузку 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();
20
ответ дан LukeH 28 August 2018 в 17:39
поделиться

Этот вопрос составляет около 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;
}
16
ответ дан atanamir 28 August 2018 в 17:39
поделиться

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

Статические методы

Предположим, что у вас несколько статических методов с таким же именем, как:

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

Вы получили только тот метод, который вам нужен, без сумасшедшей сантехники, и без поиска во времени или использования рискованных строк.

MethodInfo

Обычно в 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);

Развязка MethodInfo и типы параметров

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

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

Редактирование: очищенные объяснения, пример метода экземпляра Валери.

33
ответ дан Chris Moschini 28 August 2018 в 17:39
поделиться

Я обнаружил самый простой способ использования выражений iQueryable при вызове метода с использованием отражения. См. Ниже код:

Вы можете использовать выражение IQueryable в соответствии с требованием.

var attributeName = "CarName";
var attributeValue = "Honda Accord";

carList.FirstOrDefault(e => e.GetType().GetProperty(attributeName).GetValue(e, null) as string== attributeValue);
0
ответ дан Dilip Nannaware 28 August 2018 в 17:39
поделиться

Использовать DynamicMethods.GenericMethodInvokerMethod , GetMethod недостаточно для использования с generics

2
ответ дан garik 28 August 2018 в 17:39
поделиться

Ответ Криса Москини хорош, когда вы знаете имя метода во время компиляции. Ответ 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();
0
ответ дан Markos 28 August 2018 в 17:39
поделиться

В дополнение к ответу @ 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));  
2
ответ дан Martin Liversage 28 August 2018 в 17:39
поделиться

Пусть компилятор сделает это за вас:

var fakeExp = (Expression<Func<IQueryable<int>, IQueryable<int>>>)(q => q.Where((x, idx) => x> 2));
var mi = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition();

для индекса Where с индексом или просто оставьте второй параметр в выражении Where для объекта без

3
ответ дан MBoros 28 August 2018 в 17:39
поделиться

Ответ 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» должны быть «продолжены».

0
ответ дан Ravenous Bugblatter Beast 28 August 2018 в 17:39
поделиться

Я сделал небольшую вспомогательную функцию:

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)) 
           });
1
ответ дан Rbjz 28 August 2018 в 17:39
поделиться

Другое решение, которое может показаться вам полезным - можно получить 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. Вы можете проверить, как это работает здесь

3
ответ дан Yurec 28 August 2018 в 17:39
поделиться
Другие вопросы по тегам:

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