Каков наилучший способ вызова универсального метода, когда параметр типа неизвестен во время компиляции, а вместо этого получается динамически во время выполнения?
Рассмотрим следующий пример кода - внутри Example()
метода, какой самый краткий способ вызвать, GenericMethod<T>()
используя Type
хранимую в myType
переменной?
public class Sample
{
public void Example(string typeName)
{
Type myType = FindType(typeName);
// What goes here to call GenericMethod<T>()?
GenericMethod<myType>(); // This doesn't work
// What changes to call StaticMethod<T>()?
Sample.StaticMethod<myType>(); // This also doesn't work
}
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
c#
.net
generics
reflection
Беван
источник
источник
BindingFlags.Instance
не простоBindingFlags.NonPublic
получить метод private / internal.Ответы:
Вам нужно использовать отражение, чтобы получить метод для начала, а затем «создать» его, предоставив аргументы типа с помощью MakeGenericMethod :
Для статического метода передайте
null
в качестве первого аргументаInvoke
. Это не имеет ничего общего с общими методами - это просто нормальное отражение.Как уже отмечалось, многое из этого проще с использованием C # 4
dynamic
- если вы, конечно, можете использовать вывод типов. Это не помогает в случаях, когда вывод типа недоступен, например, точный пример в вопросе.источник
GetMethod()
по умолчанию рассматриваются только общедоступные методы экземпляров, поэтому вам может понадобитьсяBindingFlags.Static
и / илиBindingFlags.NonPublic
.BindingFlags.NonPublic | BindingFlags.Instance
(и опциональноBindingFlags.Static
).dynamic
не помогает , потому что умозаключение типа не доступно. (Нет аргументов, которые компилятор может использовать для определения аргумента типа.)Просто дополнение к оригинальному ответу. Пока это будет работать:
Это также немного опасно, потому что вы теряете проверку во время компиляции
GenericMethod
. Если вы позже выполните рефакторинг и переименуетеGenericMethod
, этот код не заметит и не будет работать во время выполнения. Также, если есть какая-либо постобработка сборки (например, запутывание или удаление неиспользуемых методов / классов), этот код может также сломаться.Итак, если вы знаете метод, на который вы ссылаетесь во время компиляции, и он не вызывается миллионы раз, поэтому накладные расходы не имеют значения, я бы изменил этот код на:
Хотя это и не очень красиво, у вас есть ссылка на время компиляции
GenericMethod
, и если вы выполните рефакторинг, удалите или сделаете что-нибудь сGenericMethod
этим, этот код продолжит работать или, по крайней мере, сломается во время компиляции (если, например, вы удалитеGenericMethod
).Другой способ сделать то же самое - создать новый класс-обертку и создать его через
Activator
. Я не знаю, есть ли лучший способ.источник
GenMethod.Method.GetGenericMethodDefinition()
вместоthis.GetType().GetMethod(GenMethod.Method.Name)
. Это немного чище и, вероятно, безопаснее.nameof(GenericMethod)
Вызов универсального метода с параметром типа, известным только во время выполнения, может быть значительно упрощен при использовании
dynamic
типа вместо API отражения.Чтобы использовать эту технику, тип должен быть известен из фактического объекта (а не только экземпляра
Type
класса). В противном случае вам нужно создать объект этого типа или использовать стандартное решение API отражения . Вы можете создать объект, используя метод Activator.CreateInstance .Если вы хотите вызвать универсальный метод, для которого при «нормальном» использовании был бы сделан вывод о его типе, то это просто сводится к приведению объекта неизвестного типа
dynamic
. Вот пример:И вот вывод этой программы:
Process
является универсальным методом экземпляра, который записывает реальный тип переданного аргумента (с помощьюGetType()
метода) и тип универсального параметра (с помощьюtypeof
оператора).Приведя аргумент объекта к
dynamic
типу, мы отложили предоставление параметра типа до времени выполнения. КогдаProcess
метод вызывается сdynamic
аргументом, компилятору не важен тип этого аргумента. Компилятор генерирует код, который во время выполнения проверяет реальные типы передаваемых аргументов (используя отражение) и выбирает лучший метод для вызова. Здесь есть только один универсальный метод, поэтому он вызывается с правильным параметром типа.В этом примере вывод такой же, как если бы вы написали:
Версия с динамическим типом определенно короче и проще для написания. Вы также не должны беспокоиться о производительности вызова этой функции несколько раз. Следующий вызов с аргументами того же типа должен быть быстрее благодаря механизму кэширования в DLR. Конечно, вы можете написать код, который кеширует вызываемых делегатов, но используя
dynamic
тип, вы получаете это поведение бесплатно.Если универсальный метод, который вы хотите вызвать, не имеет аргумента параметризованного типа (поэтому его параметр типа не может быть выведен), вы можете обернуть вызов универсального метода в вспомогательный метод, как в следующем примере:
Повышенная безопасность типов
Что действительно хорошо в использовании
dynamic
объекта в качестве замены для использования API отражения, так это то, что вы теряете только проверку времени компиляции этого конкретного типа, которую вы не знаете до времени выполнения. Другие аргументы и имя метода статически анализируются компилятором как обычно. Если вы удалите или добавите больше аргументов, измените их типы или переименуете имя метода, вы получите ошибку во время компиляции. Этого не произойдет, если вы предоставите имя метода в виде строкиType.GetMethod
и аргументы в виде массива объектовMethodInfo.Invoke
.Ниже приведен простой пример, который иллюстрирует, как некоторые ошибки могут быть обнаружены во время компиляции (закомментированный код), а другие - во время выполнения. Также показано, как DLR пытается определить, какой метод вызывать.
Здесь мы снова выполняем некоторый метод, приводя аргумент к
dynamic
типу. Только проверка типа первого аргумента откладывается до времени выполнения. Вы получите ошибку компилятора, если имя метода, который вы вызываете, не существует или если другие аргументы недопустимы (неправильное количество аргументов или неправильные типы).Когда вы передаете
dynamic
аргумент методу, этот вызов в последнее время становится связанным . Разрешение перегрузки метода происходит во время выполнения и пытается выбрать наилучшую перегрузку. Так что если вы вызываетеProcessItem
метод с объектомBarItem
типа, то вы фактически вызовете неуниверсальный метод, потому что он лучше подходит для этого типа. Однако при передаче аргументаAlpha
типа вы получите ошибку времени выполнения, потому что нет метода, который может обработать этот объект (универсальный метод имеет ограничение,where T : IItem
аAlpha
класс не реализует этот интерфейс). Но в этом все дело. Компилятор не имеет информации, что этот вызов действителен. Вы, как программист, знаете это, и вы должны убедиться, что этот код работает без ошибок.Возвращаемый тип
Когда вы вызываете не-void метод с параметром динамического типа, его возвращаемый тип, вероятно ,
dynamic
тоже будет . Так что если вы измените предыдущий пример на этот код:тогда тип объекта результата будет
dynamic
. Это потому, что компилятор не всегда знает, какой метод будет вызван. Если вы знаете тип возвращаемого значения вызова функции, вам следует неявно преобразовать его в требуемый тип, чтобы остальная часть кода была статически типизирована:Вы получите ошибку во время выполнения, если тип не соответствует.
На самом деле, если вы попытаетесь получить значение результата в предыдущем примере, вы получите ошибку времени выполнения во второй итерации цикла. Это потому, что вы пытались сохранить возвращаемое значение функции void.
источник
ProcessItem
метод имеет общие ограничения и принимает только объект, который реализуетIItem
интерфейс. Когда вы позвонитеProcessItem(new Aplha(), "test" , 1);
илиProcessItem((object)(new Aplha()), "test" , 1);
получите ошибку компилятора, но при приведении кdynamic
вам отложите эту проверку до времени выполнения.В C # 4.0 отражение не требуется, поскольку DLR может вызывать его, используя типы времени выполнения. Поскольку использование библиотеки DLR является своего рода динамической болью (вместо того, чтобы компилятор генерировал для вас код C #), среда с открытым исходным кодом Dynamitey (.net стандарт 1.5) предоставляет вам простой кэшированный доступ во время выполнения к тем же вызовам, которые генерирует компилятор. для вас.
источник
Добавляем к ответу Адриана Галлеро :
Вызов универсального метода из информации о типе включает три шага.
TLDR: вызов известного универсального метода с объектом типа может быть выполнен с помощью:
где
GenericMethod<object>
- имя метода для вызова и любой тип, который удовлетворяет общим ограничениям.(Действие) соответствует сигнатуре вызываемого метода т.е. (
Func<string,string,int>
илиAction<bool>
)Шаг 1 - получение MethodInfo для определения общего метода
Способ 1. Используйте GetMethod () или GetMethods () с соответствующими типами или флагами привязки.
Метод 2: Создайте делегат, получите объект MethodInfo и затем вызовите GetGenericMethodDefinition
Внутри класса, который содержит методы:
Из-за пределов класса, который содержит методы:
В C # имя метода, то есть «ToString» или «GenericMethod», фактически ссылается на группу методов, которые могут содержать один или несколько методов. Пока вы не предоставите типы параметров метода, неизвестно, на какой метод вы ссылаетесь.
((Action)GenericMethod<object>)
относится к делегату для конкретного метода.((Func<string, int>)GenericMethod<object>)
ссылается на другую перегрузку GenericMethodМетод 3: Создайте лямбда-выражение, содержащее выражение вызова метода, получите объект MethodInfo и затем GetGenericMethodDefinition
Это разбивается на
Создайте лямбда-выражение, где тело - это вызов нужного вам метода.
Извлеките тело и приведите к MethodCallExpression
Получить определение общего метода из метода
Шаг 2 вызывает MakeGenericMethod для создания универсального метода с соответствующими типами.
Шаг 3 вызывает метод с соответствующими аргументами.
источник
Никто не предоставил « классическое отражение », так что вот полный пример кода:
В приведенном выше
DynamicDictionaryFactory
классе есть методCreateDynamicGenericInstance(Type keyType, Type valueType)
и он создает и возвращает экземпляр IDictionary, типы ключей и значений которого точно указаны в вызове
keyType
иvalueType
.Вот полный пример того, как вызвать этот метод для создания экземпляра и использования
Dictionary<String, int>
:Когда приведенное выше консольное приложение выполняется, мы получаем правильный ожидаемый результат:
источник
Это мои 2 цента на основе ответа Гракс , но с двумя параметрами, необходимыми для универсального метода.
Предположим, ваш метод определен следующим образом в классе Helpers:
В моем случае тип U всегда является наблюдаемым объектом хранения коллекции типа T.
Поскольку у меня есть предопределенные типы, я сначала создаю «фиктивные» объекты, которые представляют наблюдаемую коллекцию (U) и объект, хранящийся в ней (T), и которые будут использоваться ниже, чтобы получить их тип при вызове Make
Затем вызовите GetMethod, чтобы найти вашу универсальную функцию:
Пока что вышеупомянутый вызов в значительной степени идентичен тому, что было объяснено выше, но с небольшой разницей, когда вам нужно передать ему несколько параметров.
Вам необходимо передать массив Type [] в функцию MakeGenericMethod, которая содержит типы «фиктивных» объектов, которые были созданы выше:
Как только это будет сделано, вам нужно вызвать метод Invoke, как указано выше.
И вы сделали. Работает шарм!
ОБНОВИТЬ:
Как подчеркнул @Bevan, мне не нужно создавать массив при вызове функции MakeGenericMethod, поскольку она принимает параметры, и мне не нужно создавать объект для получения типов, поскольку я могу просто передавать типы непосредственно в эту функцию. В моем случае, так как у меня есть типы, предопределенные в другом классе, я просто изменил свой код на:
myClassInfo содержит 2 свойства типа,
Type
которые я устанавливаю во время выполнения на основе значения перечисления, переданного конструктору, и предоставит мне соответствующие типы, которые я затем использую в MakeGenericMethod.Еще раз спасибо за выделение этого @Bevan.
источник
MakeGenericMethod()
имеют ключевое слово params, поэтому вам не нужно создавать массив; и вам не нужно создавать экземпляры для получения типов -methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
было бы достаточно.Вдохновленный ответом Enigmativity - давайте предположим, что у вас есть два (или более) класса, например
и вы хотите вызвать метод
Foo<T>
сBar
иSquare
, который объявлен какЗатем вы можете реализовать метод Extension, например:
При этом вы можете просто вызвать
Foo
как:который работает для каждого класса. В этом случае он выведет:
источник