Nano Hash - криптовалюты, майнинг, программирование

GetMethod для универсального метода

Я пытаюсь получить MethodInfo для метода Where типа Enumerable:

typeof (Enumerable).GetMethod("Where", new Type[] { 
     typeof(IEnumerable<>), 
     typeof(Func<,>) 
})

но получить ноль. Что я делаю неправильно?

27.10.2010


Ответы:


1

Однако предыдущий ответ работает в некоторых случаях:

  • Он не обрабатывает вложенные универсальные типы, такие как тип параметра Action<IEnumerable<T>>. Он будет рассматривать все Action<> как совпадения, например, string.Concat(IEnumerable<string>) и string.Concat<T>(IEnumerable<T>) будут совпадать при поиске "Concat" с типом IEnumerable<> в строковом типе. Что действительно желательно, так это рекурсивная обработка вложенных универсальных типов, при этом все общие параметры рассматриваются как соответствующие друг другу независимо от имени, но НЕ соответствуют конкретным типам.
  • Он возвращает первый совпавший метод, а не генерирует исключение, если результат неоднозначен, как это делает type.GetMethod(). Итак, вы можете получить желаемый метод, если вам повезет, а может и нет.
  • Иногда необходимо указать BindingFlags, чтобы избежать двусмысленности, например, когда метод производного класса «скрывает» метод базового класса. Обычно вы хотите найти методы базового класса, но не в специализированном случае, когда вы знаете, что метод, который вы ищете, находится в производном классе. Или вы можете знать, что ищете статический метод против экземпляра, публичный против частного и т. Д. И не хотите совпадать, если он неточный.
  • Он не устраняет другую серьезную ошибку type.GetMethods(), так как он также не выполняет поиск методов в базовых интерфейсах при поиске метода для типа интерфейса. Хорошо, может быть, это придирчивость, но это еще один серьезный недостаток GetMethods(), который стал для меня проблемой.
  • Вызов type.GetMethods() неэффективен, type.GetMember(name, MemberTypes.Method, ...) вернет только методы с совпадающим именем вместо ВСЕХ методов в типе.
  • В качестве последней придирки, имя GetGenericMethod() может вводить в заблуждение, поскольку вы можете попытаться найти неуниверсальный метод, который имеет параметр типа где-то в типе параметра из-за общего типа объявления.

Вот версия, которая решает все эти проблемы, и ее можно использовать как универсальную замену ошибочному GetMethod(). Обратите внимание, что предоставляются два метода расширения: один с BindingFlags, а другой без (для удобства).

/// <summary>
/// Search for a method by name and parameter types.  
/// Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethodExt(  this Type thisType, 
                                        string name, 
                                        params Type[] parameterTypes)
{
    return GetMethodExt(thisType, 
                        name, 
                        BindingFlags.Instance 
                        | BindingFlags.Static 
                        | BindingFlags.Public 
                        | BindingFlags.NonPublic
                        | BindingFlags.FlattenHierarchy, 
                        parameterTypes);
}

/// <summary>
/// Search for a method by name, parameter types, and binding flags.  
/// Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethodExt(  this Type thisType, 
                                        string name, 
                                        BindingFlags bindingFlags, 
                                        params Type[] parameterTypes)
{
    MethodInfo matchingMethod = null;

    // Check all methods with the specified name, including in base classes
    GetMethodExt(ref matchingMethod, thisType, name, bindingFlags, parameterTypes);

    // If we're searching an interface, we have to manually search base interfaces
    if (matchingMethod == null && thisType.IsInterface)
    {
        foreach (Type interfaceType in thisType.GetInterfaces())
            GetMethodExt(ref matchingMethod, 
                         interfaceType, 
                         name, 
                         bindingFlags, 
                         parameterTypes);
    }

    return matchingMethod;
}

private static void GetMethodExt(   ref MethodInfo matchingMethod, 
                                    Type type, 
                                    string name, 
                                    BindingFlags bindingFlags, 
                                    params Type[] parameterTypes)
{
    // Check all methods with the specified name, including in base classes
    foreach (MethodInfo methodInfo in type.GetMember(name, 
                                                     MemberTypes.Method, 
                                                     bindingFlags))
    {
        // Check that the parameter counts and types match, 
        // with 'loose' matching on generic parameters
        ParameterInfo[] parameterInfos = methodInfo.GetParameters();
        if (parameterInfos.Length == parameterTypes.Length)
        {
            int i = 0;
            for (; i < parameterInfos.Length; ++i)
            {
                if (!parameterInfos[i].ParameterType
                                      .IsSimilarType(parameterTypes[i]))
                    break;
            }
            if (i == parameterInfos.Length)
            {
                if (matchingMethod == null)
                    matchingMethod = methodInfo;
                else
                    throw new AmbiguousMatchException(
                           "More than one matching method found!");
            }
        }
    }
}

/// <summary>
/// Special type used to match any generic parameter type in GetMethodExt().
/// </summary>
public class T
{ }

/// <summary>
/// Determines if the two types are either identical, or are both generic 
/// parameters or generic types with generic parameters in the same
///  locations (generic parameters match any other generic paramter,
/// but NOT concrete types).
/// </summary>
private static bool IsSimilarType(this Type thisType, Type type)
{
    // Ignore any 'ref' types
    if (thisType.IsByRef)
        thisType = thisType.GetElementType();
    if (type.IsByRef)
        type = type.GetElementType();

    // Handle array types
    if (thisType.IsArray && type.IsArray)
        return thisType.GetElementType().IsSimilarType(type.GetElementType());

    // If the types are identical, or they're both generic parameters 
    // or the special 'T' type, treat as a match
    if (thisType == type || ((thisType.IsGenericParameter || thisType == typeof(T)) 
                         && (type.IsGenericParameter || type == typeof(T))))
        return true;

    // Handle any generic arguments
    if (thisType.IsGenericType && type.IsGenericType)
    {
        Type[] thisArguments = thisType.GetGenericArguments();
        Type[] arguments = type.GetGenericArguments();
        if (thisArguments.Length == arguments.Length)
        {
            for (int i = 0; i < thisArguments.Length; ++i)
            {
                if (!thisArguments[i].IsSimilarType(arguments[i]))
                    return false;
            }
            return true;
        }
    }

    return false;
}

Обратите внимание, что метод расширения IsSimilarType(Type) может быть опубликован и может быть полезен сам по себе. Я знаю, имя невелико - вы можете придумать лучшее, но может потребоваться очень много времени, чтобы объяснить, что оно делает. Кроме того, я добавил еще одно улучшение, проверив типы «ref» и массив (ссылки игнорируются при сопоставлении, но размеры массивов должны совпадать).

Итак, вот как Microsoft должна это сделать. Это действительно не так уж и сложно.

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

Если вы любите Linq и должны, вы можете заменить внутреннюю часть IsSimilarType() на это (превращает 8 строк в 1):

if (thisArguments.Length == arguments.Length)
    return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any();

И последнее: если вы ищете универсальный метод с универсальным параметром, например Method<T>(T, T[]), вам нужно будет найти Тип, который является универсальным параметром (IsGenericParameter == true) для передачи в качестве типа параметра (подойдет любой , из-за соответствия "подстановочным знакам"). Однако вы не можете просто сделать new Type() - вам нужно найти настоящий (или построить его с помощью TypeBuilder). Чтобы упростить эту задачу, я добавил объявление public class T и добавил логику в IsSimilarType(), чтобы проверить его и сопоставить с любым универсальным параметром. Если вам нужен T[], просто используйте T.MakeArrayType(1).

24.08.2011
  • Итак, вот как Microsoft должна была это сделать. Это действительно не так уж и сложно. Да, кроме того, что ваш код по-прежнему не работает. Если аргумент - System.Type, а переданное значение - System.RuntimeType, - не работает. 21.12.2012
  • Кэмерон, я думаю, что вы, вероятно, делаете что-то не так из-за этой проблемы. Может быть, вы вызвали GetType () для объекта Type? См. Ответ на этот пост: stackoverflow.com/questions/5737840/ 27.12.2012
  • @Ken_Beckett Нет, я просто хочу вызвать метод с подписью SomeMethod(Type t), перейдя dynamicInstance.SomeMethod(other.GetType()). Проблема в том, что IsSimilarType возвращает false для System.RuntimeType и System.Type. После устранения этой проблемы у меня возникли проблемы с вызовом методов с общими параметрами. Весь ваш код работает не во всех случаях, поэтому я бы сказал, что проблема в очень сложна. 27.12.2012
  • @CameronMacFarland Я думаю, что вы тупы, но я попробую еще раз: вы вызываете GetType (), когда не должны («другой» в вашем примере, вероятно, уже является типом). Точно так же, как ответ, на который я вам указал. Я использую этот опубликованный код в продукте, который делает практически все, что делает компилятор C #, за исключением генерации кода IL, и я запускал его на бесчисленных огромных базах кода исходных кодов C #, и у меня никогда не было ни одной проблемы с ним. Он со всем справляется правильно, если вы правильно его используете. Не должно быть необходимости заставлять его работать непосредственно с экземплярами RuntimeType вместо типов. 29.12.2012
  • @CameronMacFarland Я также должен указать, что ваша проблема с RuntimeType также будет проблемой с другими примерами здесь и, что наиболее важно, с методом MS. Кроме того, ваше неправильное использование моего кода НЕ означает, что мой код не работает или что проблема действительно настолько сложна. Кроме того, вы довольно грубо относитесь к тому, кто изо всех сил старался опубликовать удобный, полезный и бесплатный код для всеобщего использования. Если бы я только мог отозвать ваше личное право на его использование, я бы сделал это. 29.12.2012
  • @CameronMacFarland Да, из вашего первого комментария было очевидно, что у вас были какие-то личные проблемы с моим ответом, и вы хотели только пожаловаться, а не получить помощь или помочь другим. Язвительные, незрелые, грубые и непрофессиональные комментарии - недопустимое использование StackOverflow. Очевидно, что это напрасно для вас, но для других я еще раз подчеркну, что путаница между RuntimeType и Type не имеет абсолютно ничего общего с серьезностью этой проблемы, никто другой код, размещенный здесь или Microsoft, не справляется с этим (потому что это не обязательно) , и в моем коде нет ошибок. 29.12.2012
  • Я просто оставлю это здесь ... Неудачный тест на этот ответ. gist.github.com/4410158 30.12.2012
  • Это немного накалилось ... Итак, чтобы подвести итог этой дискуссии, предлагаемый здесь GetMethodExt () действительно помогает находить методы, даже если у них есть общие аргументы, в отличие от существующего GetMethod (). Однако у него есть ограничение в том, что тип, из которого он вызывается, должен соответствовать типу объекта, переданного в качестве второго аргумента - если второй аргумент находится дальше по дереву наследования, он не найдет метод. Справедливый? По-прежнему кажется полезным кодом (решает мой вариант использования), ограничение отмечено. Все счастливы? Кен, возможно, имеет смысл опубликовать это на github. 21.03.2013
  • В настоящее время не находит методов с необязательными параметрами. Решение (если необязательные параметры передаются как пустые) состоит в том, чтобы изменить тест IsSimilarType на if (!parameterInfos[i].IsOptional && !parameterInfos[i].ParameterType.IsSimilarType(parameterTypes[i])). Потребуются дополнительные изменения кода для поддержки исключения необязательных параметров. 18.06.2013
  • @CameronMacFarland в опубликованной вами ссылке на github, вы передаете typeof(object), вместо этого ваша подпись метода SomeMethod(Type t) гласит, что вы действительно должны передать typeof(Type). 08.10.2013
  • @KenBeckett, как вы вызываете свой GetMethodExt, если метод такой общий: void Pita<R, S, T>(R r, S s, T t)? А когда у вас есть две общие перегрузки void Pita<S, T>(S s, T t) и void Pita<S, T>(T t, S s), как получить MethodInfo первую перегрузку? Короче говоря, с дженериками такие штуки очень и очень тяжело. Если MS не добавит в C # какой-нибудь новый индикатор для обозначения универсальных типов, это невозможно. 10.10.2013
  • Можете ли вы сказать мне, как предыдущий ответ (Дастина Кэмпбелла) приводит к неоднозначному совпадению (и в этом случае он дает первый результат, как вы говорите)? Просто это не должно приводить к неоднозначному совпадению, поскольку мы указываем тип всех параметров. 10.10.2013
  • Кроме того, почему T.MakeArrayType(1) вместо typeof(T[])? Просто учусь, не принимайте это на свой счет. 10.10.2013
  • @nawfal Извините, но я намного превысил разумные пределы своего драгоценного времени для того, что началось как просто пожертвование кода, решающего все проблемы, связанные с этой проблемой для моего случая (который был полной реализацией компилятора C # 5.0). Моя щедрость была вознаграждена не благодарностью, а тем, что меня грубо пытали заставить его работать в сомнительных случаях использования других людей - не говоря уже о том, что мой ответ был объявлен неуместным полдюжиной участников, объявивших вопрос (неправильно) как дубликат. Теперь, один из тех же участников хочет, чтобы я уделял ему больше времени? Нет, спасибо. 12.10.2013
  • @nawfal На один из ваших трех вопросов есть четкий ответ в моем исходном посте, а на два других вопроса вам будет тривиально ответить самому. Затем, если вы обнаружите что-то полезное для других, опубликуйте это. Именно так следует использовать StackOverflow, а не как способ избежать усилий или размышлений, побуждая других делать это за вас. Я усвоил урок - слишком много невнимательных, неуважительных, ленивых или просто глупых людей. Остальным они все портят. Просто посмотрите раздел комментариев любого блога в Интернете, если вы найдете тот, в котором комментарии не отключены. 12.10.2013
  • @KenBeckett Я вижу, ты слишком легко принимаешь все на свой счет. Здесь никто не имеет к вам личной неприязни (по любой мыслимой причине!), и мы все благодарны за небольшую работу, которую здесь проделывают участники. Тем не менее, это не дает никому права заявлять о чем-то ложном. Некоторые из нас указывают вам, в чем вы ошиблись. Вместо того, чтобы подвергать сомнению наши мотивы, любезно исправьте свой ответ с помощью функции редактирования (если вы обнаружите, что что-то не так) или дайте нам ответ. 13.10.2013
  • 1. Никто не грубил с сомнительными вариантами использования. Вместо этого я просто указал вам, что это не так просто для MS 2. Я задал несколько других вопросов, основанных на вашем ответе. Если вы не грубо, вы должны дать мне преимущество сомнения в том, что я искренне знал, что не унижал вас. 13.10.2013
  • 3. И нет, закрытие вопроса означает объявление вопроса нерелевантным, а не ответа. Может быть, ты не так уж много участвовал. Голосовать за закрытие вопроса было ошибкой, которую я признал. Подумаешь? Qtns не удаляются, и вы также можете редактировать свой ответ, и вопрос по-прежнему будет получать просмотры. Я тоже пометил, чтобы он открылся заново (кстати, этот вопрос в любом случае дублирует многие другие вопросы). 4. Разделы комментариев предназначены для пояснений и предложений по улучшению. Если это делает его образовательным, сэр, я, как и любой другой участник, интересующийся кодом, умираю от желания получить образование. 13.10.2013
  • 5. Пожалуйста, скажите мне, на какой из моих вопросов дан ответ в вашем ответе. 6. Нет, опять же, если я знаю ответ, я бы отправил его. Незнание - это не грех. Грубость есть. Я здесь только потому, что не знаю ответа. Наконец, Беккет, пожалуйста, просмотрите сообщения здесь, на SO, чтобы обнаружить, что люди действительно предлагают улучшения и ставят под сомнение сомнительные ответы (или их часть). Это в игре. И да, я один из тех парней, которые проголосовали за ваш ответ за то, что это действительно хороший ответ. Но у меня есть вопросы, к сожалению, и это не лень, а его незнание. 13.10.2013

  • 2

    К сожалению, обобщения не поддерживаются в .NET Reflection. В этом конкретном случае вам нужно вызвать GetMethods, а затем отфильтровать набор результатов для метода, который вы ищете. Метод расширения, подобный следующему, должен помочь.

    public static class TypeExtensions
    {
        private class SimpleTypeComparer : IEqualityComparer<Type>
        {
            public bool Equals(Type x, Type y)
            {
                return x.Assembly == y.Assembly &&
                    x.Namespace == y.Namespace &&
                    x.Name == y.Name;
            }
    
            public int GetHashCode(Type obj)
            {
                throw new NotImplementedException();
            }
        }
    
        public static MethodInfo GetGenericMethod(this Type type, string name, Type[] parameterTypes)
        {
            var methods = type.GetMethods();
            foreach (var method in methods.Where(m => m.Name == name))
            {
                var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
    
                if (methodParameterTypes.SequenceEqual(parameterTypes, new SimpleTypeComparer()))
                {
                    return method;
                }
            }
    
            return null;
        }
    }
    

    С этим будет работать следующий код:

    typeof(Enumerable).GetGenericMethod("Where", new Type[] { typeof(IEnumerable<>), typeof(Func<,>) });
    
    27.10.2010
  • Или в одной строке верните type.GetMethods().FirstOrDefault(m => m.Name == name && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes, new SimpleTypeComparer())); :) Было бы лучше добавить params к последнему параметру. 09.10.2013
  • Вместо того, чтобы создавать SimpleTypeComparer каждый раз в цикле, сделайте его статическим полем класса для повышения эффективности. 27.02.2021
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..