Как вызвать generic метод по Type в C#: понятный туториал

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: когда код говорит «не знаю»

Представьте ситуацию: вы пишете на C# библиотеку для сериализации. У вас есть метод Serialize<T>(T obj), который умеет превращать любой объект в строку. Всё прекрасно, пока вы вызываете его с конкретным типом: Serialize<User>(user). Но что делать, если тип объекта становится известен только во время выполнения программы?

Это классическая задача, которую задают тысячи разработчиков на Stack Overflow: «How do I call a generic method using a Type variable?». На первый взгляд, решение кажется простым, но C# — строго типизированный язык, и просто подставить переменную типа в угловые скобки не получится.

В этом туториале мы разберём, как правильно вызывать generic методы через рефлексию C# с помощью MakeGenericMethod. Вы узнаете не только синтаксис, но и когда это действительно нужно, а когда лучше поискать альтернативы.

Почему нельзя просто написать Method<type>()?

Давайте сразу развеем иллюзии. Следующий код не скомпилируется:

Type runtimeType = someObject.GetType();
// Ошибка компиляции: "Тип 'runtimeType' не является допустимым"
SomeMethod<runtimeType>();

Почему? Потому что generic-параметры в C# — это метаданные времени компиляции. Компилятор должен точно знать, с каким типом он работает, чтобы сгенерировать код. Переменная Type — это объект, который живёт только в runtime. Для компилятора это просто переменная, а не тип.

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

Пошаговый туториал: вызываем generic метод через Type

Разберём задачу на конкретном примере. Допустим, у нас есть сервис для обработки данных:

public class DataProcessor
{
    public string Process<T>(T data)
    {
        return $"Обработан {typeof(T).Name}: {data}";
    }
}

Мы хотим вызвать Process<T>, когда тип T известен только в runtime, например, из typeof(string) или someObject.GetType().

Шаг 1. Получаем MethodInfo для generic метода

Сначала нам нужно получить объект, представляющий наш метод:

DataProcessor processor = new DataProcessor();
Type processorType = typeof(DataProcessor);

// Ищем метод по имени. Важно: указываем BindingFlags, если метод не public
MethodInfo? methodInfo = processorType.GetMethod("Process");

Важно! Если у вас несколько перегрузок метода, придётся уточнить поиск с помощью GetMethods() и фильтрации. В нашем случае метод один — всё просто.

Шаг 2. Превращаем generic метод в конкретный с помощью MakeGenericMethod

Вот он, главный герой статьи — метод MakeGenericMethod. Он принимает массив типов и возвращает сконструированный (конкретный) метод:

Type runtimeType = typeof(int); // или любой другой тип, полученный динамически
MethodInfo? genericMethod = methodInfo?.MakeGenericMethod(runtimeType);

Теперь genericMethod — это полноценный метод Process<int>, готовый к вызову.

Шаг 3. Вызываем метод через Invoke

Финальный аккорд — использование Invoke:

object? result = genericMethod?.Invoke(processor, new object[] { 42 });
Console.WriteLine(result); // Выведет: "Обработан Int32: 42"

Обратите внимание:

  • Первый параметр Invoke — экземпляр объекта (для статических методов — null).
  • Второй параметр — массив аргументов метода. Даже если аргумент один, он должен быть в массиве.
  • Результат возвращается как object? — вам может понадобиться приведение типа.

Полный рабочий пример

Соберём всё вместе в одну программу:

using System;
using System.Reflection;

public class DataProcessor
{
    public string Process<T>(T data)
    {
        return $"Обработан {typeof(T).Name}: {data}";
    }
}

class Program
{
    static void Main()
    {
        var processor = new DataProcessor();
        Type processorType = typeof(DataProcessor);
        
        // Получаем generic метод
        MethodInfo? methodInfo = processorType.GetMethod("Process");
        if (methodInfo == null) return;

        // Допустим, тип мы узнаём динамически
        Type runtimeType = typeof(double);
        
        // Создаём конкретную версию метода
        MethodInfo? concreteMethod = methodInfo.MakeGenericMethod(runtimeType);
        
        // Вызываем
        object? result = concreteMethod?.Invoke(processor, new object[] { 3.14 });
        Console.WriteLine(result); // "Обработан Double: 3.14"
    }
}

Этот код работает и решает вашу задачу. Но давайте разберёмся с подводными камнями.

Сложные случаи и подводные камни

1. Generic методы с несколькими параметрами

Если метод объявлен как void Foo<T1, T2>(T1 a, T2 b), то в MakeGenericMethod нужно передать массив из двух типов:

MethodInfo? concrete = method.MakeGenericMethod(typeof(int), typeof(string));

2. Проверка на generic-метод

Перед вызовом MakeGenericMethod стоит убедиться, что метод действительно является generic:

if (methodInfo != null && methodInfo.IsGenericMethod)
{
    // безопасно вызываем MakeGenericMethod
}

3. Производительность

Рефлексия — не самый быстрый механизм. Если вы вызываете MakeGenericMethod в цикле тысячи раз, это может стать узким местом. В таких случаях рассмотрите:

  • Кэширование полученного MethodInfo.
  • Использование динамических делегатов (создание делегата один раз, вызов много раз).
  • Переход на dynamic (если производительность не критична).

4. Generic-методы с ограничениями (constraints)

Если ваш generic метод имеет ограничения, например where T : class, и вы пытаетесь подставить typeof(int) — получите исключение во время выполнения. Проверяйте совместимость типов заранее.

Альтернативные подходы: когда рефлексия не нужна

Прежде чем лезть в рефлексию, спросите себя: «А можно ли решить задачу иначе?» Вот несколько альтернатив:

  • Использование интерфейсов: если все ваши типы реализуют общий интерфейс, можно принимать IProcessable вместо generic.
  • Базовый класс или аб

Блоги

Рекомендаций по книгам