Как вызвать generic метод через Type переменную в C#?
Правильный вызов generic метода через рефлексию
Ваш подход в целом верный, но есть несколько нюансов. Давайте разберем правильный способ шаг за шагом.
Шаг 1: Получение MethodInfo без указания generic-параметров
Когда вы вызываете GetMethod("Deserialize"), вы получаете неконкретизированный generic-метод. Но есть важный момент: если у вас есть перегрузки метода (например, Deserialize<T> и просто Deserialize), то нужно явно указать, что мы ищем generic-версию. Лучше использовать GetMethods() и фильтровать:
// Получаем все методы с именем "Deserialize"
MethodInfo method = typeof(MyParser)
.GetMethods()
.First(m => m.Name == "Deserialize" && m.IsGenericMethodDefinition);
Шаг 2: Создание конкретного generic-метода
Теперь, когда у нас есть MethodInfo для Deserialize<>, мы можем закрыть его нужным типом:
Type targetType = typeof(MyClass);
MethodInfo genericMethod = method.MakeGenericMethod(targetType);
Шаг 3: Вызов метода
При вызове Invoke нужно передать экземпляр класса (если метод нестатический) и массив параметров:
object result = genericMethod.Invoke(this, new object[] { dataString });
Полный рабочий пример
public class MyParser
{
public T Deserialize<T>(string data) where T : class
{
return JsonConvert.DeserializeObject<T>(data);
}
public object DeserializeByType(Type targetType, string data)
{
// Шаг 1: находим generic-метод
MethodInfo method = typeof(MyParser)
.GetMethods()
.First(m => m.Name == "Deserialize" && m.IsGenericMethodDefinition);
// Шаг 2: закрываем generic-параметр
MethodInfo genericMethod = method.MakeGenericMethod(targetType);
// Шаг 3: вызываем
return genericMethod.Invoke(this, new object[] { data });
}
}
// Использование:
var parser = new MyParser();
Type type = typeof(MyClass);
object result = parser.DeserializeByType(type, "{\"name\":\"test\"}");
var myObj = (MyClass)result; // приведение к нужному типу
Важные моменты:
- Проверка на null: всегда проверяйте, что
methodне равенnull. Если метод не найден, вы получите исключение. - Производительность: рефлексия медленная. Если вы вызываете этот метод много раз, кэшируйте
MethodInfoиgenericMethodв словаре. - Generic-ограничения: если у вас есть ограничения (
where T : class), убедитесь, чтоtargetTypeим соответствует, иначе будет исключение. - Возвращаемое значение:
Invokeвозвращаетobject, поэтому вам придется привести результат к нужному типу.
Кэширование для производительности
private readonly Dictionary<Type, MethodInfo> _cache = new();
public object DeserializeByTypeCached(Type targetType, string data)
{
if (!_cache.TryGetValue(targetType, out var method))
{
var methodDef = typeof(MyParser)
.GetMethods()
.First(m => m.Name == "Deserialize" && m.IsGenericMethodDefinition);
method = methodDef.MakeGenericMethod(targetType);
_cache[targetType] = method;
}
return method.Invoke(this, new object[] { data });
}
Этот подход сработает для любого generic-метода, а не только для десериализации. Главное — правильно получить неконкретизированный MethodInfo и закрыть его нужным типом.
Альтернатива: использование интерфейсов или абстрактного базового класса
Если вы контролируете код и типы известны заранее (хотя и не на этапе компиляции), можно избежать рефлексии через паттерн "посетитель" или фабрику.
Создайте не-generic интерфейс:
public interface IDeserializer
{
object Deserialize(string data);
}
public class Deserializer<T> : IDeserializer where T : class
{
public object Deserialize(string data)
=> JsonConvert.DeserializeObject<T>(data);
}
Затем используйте словарь для сопоставления типов с десериализаторами:
private readonly Dictionary<Type, IDeserializer> _deserializers = new()
{
{ typeof(MyClass), new Deserializer<MyClass>() },
{ typeof(OtherClass), new Deserializer<OtherClass>() }
};
public object Deserialize(Type type, string data)
{
if (_deserializers.TryGetValue(type, out var deserializer))
return deserializer.Deserialize(data);
throw new NotSupportedException($"Type {type} is not supported");
}
Плюсы: без рефлексии, быстрее, типобезопасно на этапе регистрации.
Минусы: нужно заранее регистрировать все типы. Подходит, если количество типов ограничено и известно.