Как передать общий тип в общий метод?

Почему я не могу позвонить SomeGenericMethod<SomeGenericType<>>?

class NotGeneric { }

class Generic<T> { }

class Program
{
    static void Main(string[] args)
    {
        PrintType(typeof(NotGeneric));
        PrintType(typeof(Generic<>));
        PrintType<NotGeneric>();
        PrintType<Generic<>>(); // compiler goes crazy here
    }

    static void PrintType<T>()
    {
        Console.WriteLine(typeof(T));
    }

    static void PrintType(Type t)
    {
        Console.WriteLine(t);
    }
}

Ответ 1

Почему я не могу позвонить SomeGenericMethod<SomeGenericType<>>

Самый простой ответ: потому что спецификация языка говорит так:

4.4 Построенные типы

Объявление общего типа само по себе обозначает несвязанный общий тип, который используется как "схема" для формирования множества разных типов путем применения аргументов типа. Аргументы типа записываются в угловые скобки (< и > ) сразу же после имени родового типа. Тип, который включает по крайней мере один аргумент типа, называется построенным типом. Построенный тип может использоваться в большинстве мест на языке, на котором может отображаться имя типа. Несвязанный общий тип может использоваться только в типе-выражении (§7.6.11).

4.4.3 Связанные и несвязанные типы

Термин "несвязанный тип" относится к неродному типу или несвязаному родовому типу. Термин связанный тип относится к неродному типу или построенному типу. Несвязанный тип относится к объекту, объявленному объявлением типа. Unbound generic type сам по себе не является типом и не может использоваться как тип переменной, аргумент или возвращаемое значение или как базовый тип. Единственной конструкцией, в которой может ссылаться несвязанный общий тип, является тип выражения (§7.6.11).

Почему спецификация С# так говорит? Потому что спецификация CLI также определяет:

II.9.4 Создание родовых типов

...

CLI не поддерживает частичную реализацию типичных типов. И общие типы не должны казаться неосведомленными где-либо в блоках подписи метаданных.

Хорошо, теперь позвольте остановиться на законных. Фактический ответ на ваш вопрос:

Когда вы вызываете SomeMethod<SomeType>() в первый раз, CLR вызывает JIT-компилятор, который будет читать SomeMethod<T>, подставить T и создать новый исполняемый код, который представляет SomeMethod<SomeType>.

Когда вы вызываете SomeMethod<SomeOtherType>(), процесс должен быть повторен, новый код будет создан для SomeMethod<SomeOtherType>.

Вы не можете просто создать допустимый код для несвязанного общего типа типа Generic<>. В общем случае JIT не может знать, какое представление кода генерировать, не зная T.

  • Если T является string, и ваш метод объявляет локальную переменную типа T, JIT будет выделять пространство стека или использовать регистр с размером собственного указателя.
  • Если T является Guid, JIT должен будет выделить пространство стека для фиксированного значения 128 бит. Он не может генерировать код, если он не знает, что будет T.

Следовательно, вы не можете генерировать исполняемый код для несвязанного типа. Почему вы, возможно, могли бы сделать именно это для предоставленного вами фрагмента, для этого потребуется специальная оболочка, и ваш код вызова перестанет работать, если вы добавили локальный T в метод PrintType. Поскольку генераторы должны быть повторно использованы для сборок, вызывающий код не может принимать ничего о вызываемом методе.

Ответ 2

Компилятор нуждается в специализированном типе, чтобы специализировать общий метод, а Generic<> не является специализированным, он является общим. Таким образом, хотя Generic<> является типом, он не специализируется на специализации обобщенного метода.

Ответ 3

PrintType<Generic<YOU NEED TO PUT TYPE HERE>>();

Ответ 4

попробуйте что-то вроде этого:

public class Generic<T>
{
    public void PrintType(T param)
    {
        Console.WriteLine(param.GetType().Name);
    }
}