Как лучше всего вызвать универсальный метод, когда параметр типа неизвестен во время компиляции, но вместо этого получается динамически во время выполнения?
Рассмотрим следующий пример кода - какой самый краткий способ вызвать 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>()
{
//...
}
}
Вам также понадобится BindingFlags.Instance, а не только BindingFlags.NonPublic, чтобы получить частный / внутренний метод.
Современная версия этого вопроса: stackoverflow.com/q/2433436/103167
@Peter Mortensen - кстати, я использовал пробелы перед '?' чтобы отделить английские части от неанглийских (C#) частей; ИМХО удаление пробела делает его похожим на? является частью кода. Если бы не было кода, я бы конечно согласился удалить пробелы, но в этом случае ...
Мы можем определить универсальный метод, а затем использовать метод GetMethod, чтобы получить всю информацию об универсальном методе и использовать ее.





Вам нужно использовать отражение, чтобы начать с метода, а затем «сконструировать» его, указав аргументы типа с помощью MakeGenericMethod:
MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.GenericMethod));
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Для статического метода передайте null в качестве первого аргумента в Invoke. Это не имеет ничего общего с универсальными методами - это просто нормальное отражение.
Как уже отмечалось, многое из этого проще, чем в C# 4 с использованием dynamic - если, конечно, вы можете использовать вывод типов. Это не помогает в тех случаях, когда вывод типа недоступен, например, в точном примере в вопросе.
+1; обратите внимание, что GetMethod() по умолчанию рассматривает только общедоступные методы экземпляра, поэтому вам может потребоваться BindingFlags.Static и / или BindingFlags.NonPublic.
Правильная комбинация флагов - BindingFlags.NonPublic | BindingFlags.Instance (и, возможно, BindingFlags.Static).
@LarsKemmann: Почему NonPublic, когда желаемый метод является общедоступным?
Извините, я должен был указать; это было в ответ на комментарий @Jon of All Trades о том, как получить доступ к непубличным методам. Ваш ответ, конечно же, правильный, без каких-либо привязок для общедоступных методов.
Вопрос, который становится помеченным как обман, заключается в том, как это сделать с помощью статических методов - и технически вопрос здесь. Первый параметр generic.Invoke () должен иметь значение null при вызове статических методов. Первый параметр необходим только при вызове методов экземпляра.
@ChrisMoschini: Добавил это к ответу.
Этот метод является излишним для многих случаев использования, использование dynamic может решить множество проблем без использования отражения. См. Ответ @Mariusz (3-й).
@gzou: Теперь это правда, да. Хотя посмотрите на дату вопроса и ответьте ...
@JonSkeet Да, просто говорю это людям, прибывающим сюда из Google, таким как я.
@gzou: Я кое-что добавил к ответу, но обратите внимание, что для вызова общих методов в вопросеdynamic не помогает, потому что вывод типа недоступен. (Нет аргументов, которые компилятор может использовать для определения аргумента типа.)
@JonSkeet, ты можешь сделать это функцией?
@Demodave: Я не уверен, что вы имеете в виду - просто оберните этот код в метод? Вы пробовали делать это самостоятельно?
@JonSkeet Это не работает, если есть 2 метода с одинаковым именем, один общий и один не общий, пример находится в классе DbContext EntityFramework: общедоступный виртуальный DbSet <TEntity> Set <TEntity> () где TEntity: class; общедоступный виртуальный набор DbSet (Тип entityType); Вызов typeof (DbContext) .GetMethod ("Set") приводит к исключению AmbigiousMatchException.
@SalmanHasratKhan: Это сработает, если у вас есть правильный метод для начала - вам просто нужно вызвать GetMethods и найти правильный метод, основанный на том, что вы о нем знаете. Вызов MakeGenericMethod будет таким же.
@JonSkeet, это правильно. Мне пришлось использовать GetMethods с запросом, чтобы убедиться, что свойство IsGeneric метода истинно, чтобы выбрать правильный метод.
@SalmanHasratKhan: Верно. Вам нужно было бы сделать что-то подобное, если бы у вас был метод, перегруженный другими способами, даже если все методы были универсальными.
@JonSkeet должен вовремя, чтобы избежать вызова, выполняющего отражение, вы можете сохранить объект в динамической переменной и после этого вызвать метод. Единственное, что компилятор не проверяет типобезопасность. Есть ли способ уточнить свой ответ на этот вопрос, если вы можете или не можете выполнить вызов универсального метода, такого как Set <TEntity>, с использованием dynamic? в этом случае TEntity должен быть статическим типом, и этот вызов недопустим. Set <runtimeobj.GetType ()>
@Zinov: Боюсь, я не совсем понимаю, о чем вы спрашиваете. Это, наверное, лучше было бы выразить в новом вопросе с примером.
Также хорошей практикой будет получить такой метод: MethodInfo method = typeof (Sample) .GetMethods (). Single (method => method.Name == nameof (Sample.GenericMethod));
Просто дополнение к исходному ответу. Пока это будет работать:
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Это также немного опасно, поскольку вы теряете проверку времени компиляции для GenericMethod. Если позже вы выполните рефакторинг и переименуете GenericMethod, этот код не заметит и не сработает во время выполнения. Кроме того, если есть какая-либо пост-обработка сборки (например, обфускация или удаление неиспользуемых методов / классов), этот код тоже может сломаться.
Итак, если вы знаете метод, на который вы ссылаетесь во время компиляции, и он не вызывается миллионы раз, поэтому накладные расходы не имеют значения, я бы изменил этот код на:
Action<> GenMethod = GenericMethod<int>; //change int by any base type
//accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Хотя это и не очень красиво, у вас есть ссылка на GenericMethod во время компиляции, и если вы проведете рефакторинг, удалите или сделаете что-нибудь с GenericMethod, этот код будет продолжать работать или, по крайней мере, сломаться во время компиляции (если, например, вы удалите GenericMethod).
Другой способ сделать то же самое - создать новый класс-оболочку и создать его через Activator. Не знаю, есть ли способ лучше.
В случаях, когда для вызова метода используется отражение, обычно само имя метода обнаруживается другим методом. Знать заранее название метода нечасто.
Что ж, я согласен с обычным использованием отражения. Но исходный вопрос заключался в том, как вызвать «GenericMethod <myType> ()». Если бы этот синтаксис был разрешен, нам вообще не понадобился бы GetMethod (). Но на вопрос «как мне написать« GenericMethod <myType> »? Я думаю, что ответ должен включать способ избежать потери связи времени компиляции с GenericMethod. Теперь, является ли этот вопрос обычным или нет, я не знаю, но Я знаю, что вчера у меня была именно эта проблема, и поэтому я столкнулся с этим вопросом.
Вы можете использовать GenMethod.Method.GetGenericMethodDefinition() вместо this.GetType().GetMethod(GenMethod.Method.Name). Он немного чище и, наверное, безопаснее.
Что означает myType в вашем примере?
«myType» - это переменная, содержащая Тип. Если бы мы знали тип, мы могли бы выполнить GenericMethod <Type> (), но поскольку у нас есть тип в переменной, мы используем отражение.
Теперь вы можете использовать nameof(GenericMethod)
Когда я пробую первую строку Action <> GenMethod = GenericMethod <int>; Я получаю ошибку компилятора CS7003 Неожиданное использование несвязанного общего имени для левой стороны и CS0123 Отсутствие перегрузки для GenericMethod соответствует делегату Action <T> для правой. Я использую .Net 4.5.
Как сделать Action<> GenMethod = GenericMethod<Type> в VB?
В C# 4.0 отражение не требуется, поскольку DLR может вызывать его, используя типы времени выполнения. Поскольку использование библиотеки DLR является своего рода проблемой динамически (вместо компилятора C#, генерирующего код для вас), фреймворк Динамитей с открытым исходным кодом (стандарт .net 1.5) дает вам простой кэшированный доступ во время выполнения к тем же вызовам, которые генерирует компилятор. для тебя.
var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));
var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));
Вызов универсального метода с параметром типа, известным только во время выполнения, можно значительно упростить, используя тип dynamic вместо API отражения.
Чтобы использовать этот метод, тип должен быть известен по фактическому объекту (а не только по экземпляру класса Type). В противном случае вам придется создать объект этого типа или использовать стандартный API отражения решение. Вы можете создать объект, используя метод Activator.CreateInstance.
Если вы хотите вызвать универсальный метод, тип которого при "нормальном" использовании должен был бы быть определен, тогда дело просто доходит до преобразования объекта неизвестного типа в dynamic. Вот пример:
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
И вот результат этой программы:
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process - это универсальный метод экземпляра, который записывает реальный тип переданного аргумента (с помощью метода GetType()) и тип универсального параметра (с помощью оператора typeof).
Приведя аргумент объекта к типу dynamic, мы отложили предоставление параметра типа до времени выполнения. Когда метод Process вызывается с аргументом dynamic, компилятор не заботится о типе этого аргумента. Компилятор генерирует код, который во время выполнения проверяет реальные типы переданных аргументов (с помощью отражения) и выбирает лучший метод для вызова. Здесь есть только один универсальный метод, поэтому он вызывается с соответствующим параметром типа.
В этом примере результат такой же, как если бы вы написали:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
Версия с динамическим типом определенно короче и ее легче писать. Также не стоит беспокоиться о производительности многократного вызова этой функции. Следующий вызов с аргументами того же типа должен быть быстрее благодаря механизму кеширование в DLR. Конечно, вы можете написать код, который кеширует вызываемые делегаты, но, используя тип dynamic, вы получаете такое поведение бесплатно.
Если общий метод, который вы хотите вызвать, не имеет аргумента параметризованного типа (поэтому его параметр типа не может быть выведен), вы можете заключить вызов универсального метода во вспомогательный метод, как в следующем примере:
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
Что действительно замечательно в использовании объекта dynamic в качестве замены для использования API отражения, так это то, что вы теряете только проверку времени компиляции этого конкретного типа, о котором вы не знаете до времени выполнения. Остальные аргументы и имя метода статически анализируются компилятором, как обычно. Если вы удалите или добавите дополнительные аргументы, измените их типы или переименуйте имя метода, вы получите ошибку времени компиляции. Этого не произойдет, если вы предоставите имя метода в виде строки в Type.GetMethod и аргументы в виде массива объектов в MethodInfo.Invoke.
Ниже приведен простой пример, показывающий, как некоторые ошибки могут быть обнаружены во время компиляции (закомментированный код), а другие - во время выполнения. Он также показывает, как DLR пытается решить, какой метод вызвать.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
Здесь мы снова выполняем какой-то метод, приводя аргумент к типу dynamic. На время выполнения откладывается только проверка типа первого аргумента. Вы получите ошибку компилятора, если имя вызываемого метода не существует или если другие аргументы недействительны (неправильное количество аргументов или неправильные типы).
Когда вы передаете аргумент dynamic методу, это вызов недавно связанный. Разрешение перегрузки метода происходит во время выполнения и пытается выбрать лучшую перегрузку. Поэтому, если вы вызываете метод ProcessItem с объектом типа BarItem, вы фактически вызываете неуниверсальный метод, потому что он лучше подходит для этого типа. Однако вы получите ошибку времени выполнения, когда передадите аргумент типа Alpha, потому что нет метода, который мог бы обрабатывать этот объект (общий метод имеет ограничение where T : IItem, а класс Alpha не реализует этот интерфейс). Но в том-то и дело. У компилятора нет информации о том, что этот вызов действителен. Вы, как программист, знаете это и должны убедиться, что этот код работает без ошибок.
Когда вы вызываете непустой метод с параметром динамического типа, его возвращаемый тип, вероятно, будет быть dynamic тоже. Итак, если вы измените предыдущий пример на этот код:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
то тип объекта результата будет dynamic. Это потому, что компилятор не всегда знает, какой метод будет вызван. Если вы знаете тип возвращаемого значения вызова функции, вам следует преобразовать его в требуемый тип, чтобы остальная часть кода была статически типизирована:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
Если тип не совпадает, вы получите сообщение об ошибке выполнения.
Фактически, если вы попытаетесь получить значение результата в предыдущем примере, вы получите ошибку времени выполнения на второй итерации цикла. Это потому, что вы пытались сохранить возвращаемое значение функции void.
Мариуш сбит с толку: «Однако вы получите ошибку времени выполнения, если передадите аргумент типа Alpha, потому что нет метода, который мог бы обрабатывать этот объект». Если я вызываю var a = new Alpha () ProcessItem (a, «test» + i , i) Почему бы универсальному методу ProcessItem не справиться с этим эффективно, выводя «Общий элемент процесса»?
@AlexEdelstein Я отредактировал свой ответ, чтобы немного уточнить. Это потому, что общий метод ProcessItem имеет общее ограничение и принимает только объект, реализующий интерфейс IItem. Когда вы вызовете ProcessItem(new Aplha(), "test" , 1); или ProcessItem((object)(new Aplha()), "test" , 1);, вы получите ошибку компилятора, но при преобразовании в dynamic вы откладываете эту проверку до времени выполнения.
Отличный ответ и объяснение, отлично работает для меня. Намного лучше, чем принятый ответ, короче, чтобы писать, эффективнее и безопаснее.
Добавление к Ответ Адриана Галлеро:
Вызов универсального метода из информации о типе включает три шага.
((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(typeof(string))
.Invoke(this, null);
где GenericMethod<object> - это имя вызываемого метода и любой тип, удовлетворяющий общим ограничениям.
(Действие) соответствует сигнатуре вызываемого метода, например (Func<string,string,int> или Action<bool>)
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
Изнутри класса, который содержит методы:
MethodInfo method = ((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
Извне класса, который содержит методы:
MethodInfo method = ((Action)(new Sample())
.GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)Sample.StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
В C# имя метода, то есть «ToString» или «GenericMethod», фактически относится к группе методов, которые могут содержать один или несколько методов. Пока вы не укажете типы параметров метода, неизвестно, какие метод, о котором вы говорите.
((Action)GenericMethod<object>) относится к делегату для определенного метода. ((Func<string, int>)GenericMethod<object>)
относится к другой перегрузке GenericMethod
MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
(Sample v) => v.GenericMethod<object>()
)).Body).Method.GetGenericMethodDefinition();
Это разбивается на
Создайте лямбда-выражение, в котором тело является вызовом желаемого метода.
Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
Извлеките тело и приведите к MethodCallExpression
MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;
Получить определение универсального метода из метода
MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Это мои 2 цента на основе Ответ Гракса, но с двумя параметрами, необходимыми для универсального метода.
Предположим, ваш метод определен в классе Helpers следующим образом:
public class Helpers
{
public static U ConvertCsvDataToCollection<U, T>(string csvData)
where U : ObservableCollection<T>
{
//transform code here
}
}
В моем случае тип U всегда является наблюдаемой коллекцией, хранящей объект типа T.
Поскольку у меня есть предопределенные типы, я сначала создаю «фиктивные» объекты, которые представляют наблюдаемую коллекцию (U) и объект, хранящийся в ней (T), и которые будут использоваться ниже для получения их типа при вызове Make
object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);
Затем вызовите GetMethod, чтобы найти свою универсальную функцию:
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
Пока что вышеупомянутый вызов в значительной степени идентичен тому, что было объяснено выше, но с небольшой разницей, когда вам нужно передать ему несколько параметров.
Вам необходимо передать массив Type [] в функцию MakeGenericMethod, которая содержит типы «фиктивных» объектов, которые были созданы выше:
MethodInfo generic = method.MakeGenericMethod(
new Type[] {
myCollection.GetType(),
myObject.GetType()
});
Как только это будет сделано, вам нужно вызвать метод Invoke, как упоминалось выше.
generic.Invoke(null, new object[] { csvData });
И вы сделали. Работает шарм!
Обновлено:
Как подчеркнул @Bevan, мне не нужно создавать массив при вызове функции MakeGenericMethod, поскольку она принимает параметры, и мне не нужно создавать объект для получения типов, так как я могу просто передавать типы непосредственно в эту функцию. В моем случае, поскольку у меня есть типы, предопределенные в другом классе, я просто изменил свой код на:
object myCollection = null;
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
MethodInfo generic = method.MakeGenericMethod(
myClassInfo.CollectionType,
myClassInfo.ObjectType
);
myCollection = generic.Invoke(null, new object[] { csvData });
myClassInfo содержит 2 свойства типа Type, которые я устанавливаю во время выполнения на основе значения перечисления, переданного конструктору, и предоставит мне соответствующие типы, которые я затем использую в MakeGenericMethod.
Еще раз спасибо за выделение этого @Bevan.
Аргументы MakeGenericMethod() содержат ключевое слово параметры, поэтому вам не нужно создавать массив; вам также не нужно создавать экземпляры для получения типов - methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject)) будет достаточно.
Никто не предоставил решение "классическое отражение", поэтому вот полный пример кода:
using System;
using System.Collections;
using System.Collections.Generic;
namespace DictionaryRuntime
{
public class DynamicDictionaryFactory
{
/// <summary>
/// Factory to create dynamically a generic Dictionary.
/// </summary>
public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
{
//Creating the Dictionary.
Type typeDict = typeof(Dictionary<,>);
//Creating KeyValue Type for Dictionary.
Type[] typeArgs = { keyType, valueType };
//Passing the Type and create Dictionary Type.
Type genericType = typeDict.MakeGenericType(typeArgs);
//Creating Instance for Dictionary<K,T>.
IDictionary d = Activator.CreateInstance(genericType) as IDictionary;
return d;
}
}
}
Вышеупомянутый класс DynamicDictionaryFactory имеет метод
CreateDynamicGenericInstance(Type keyType, Type valueType)
и он создает и возвращает экземпляр IDictionary, типы ключей и значений которого точно указаны в вызове keyType и valueType.
Вот полный пример, как вызвать этот метод для создания и использования Dictionary<String, int>:
using System;
using System.Collections.Generic;
namespace DynamicDictionary
{
class Test
{
static void Main(string[] args)
{
var factory = new DictionaryRuntime.DynamicDictionaryFactory();
var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));
var typedDict = dict as Dictionary<String, int>;
if (typedDict != null)
{
Console.WriteLine("Dictionary<String, int>");
typedDict.Add("One", 1);
typedDict.Add("Two", 2);
typedDict.Add("Three", 3);
foreach(var kvp in typedDict)
{
Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
}
}
else
Console.WriteLine("null");
}
}
}
Когда вышеуказанное консольное приложение выполняется, мы получаем правильный ожидаемый результат:
Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
Вдохновленный Ответ загадочности - предположим, у вас есть два (или более) класса, например
public class Bar { }
public class Square { }
и вы хотите вызвать метод Foo<T> с Bar и Square, который объявлен как
public class myClass
{
public void Foo<T>(T item)
{
Console.WriteLine(typeof(T).Name);
}
}
Затем вы можете реализовать Способ расширения, например:
public static class Extension
{
public static void InvokeFoo<T>(this T t)
{
var fooMethod = typeof(myClass).GetMethod("Foo");
var tType = typeof(T);
var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
fooTMethod.Invoke(new myClass(), new object[] { t });
}
}
При этом вы можете просто вызвать Foo, например:
var objSquare = new Square();
objSquare.InvokeFoo();
var objBar = new Bar();
objBar.InvokeFoo();
который работает для каждого класса. В этом случае он выведет:
Square
Bar
Я попробовал решение Джона и не смог заставить его работать, пока не сделал общий метод общедоступным в своем классе. Я знаю, что другой Джон ответил, что вам нужно указать флаги привязки, но это не помогло.