Ответ 1
Это интересный вопрос, но, к сожалению, ваш подход к "обману" системы не повысит эффективность вашей программы. Если бы это было возможно, компилятор мог бы сделать это для нас с относительной легкостью!
Вы правы, что при вызове IList<T>
через ссылку на интерфейс, что методы отправляются во время выполнения и поэтому не могут быть встроены. Поэтому вызовы методов IList<T>
, такие как Count
, и индексатор будут вызваны через интерфейс.
С другой стороны, неверно, что вы можете достичь любого преимущества производительности (по крайней мере, не с нынешним компилятором С# и .NET CLR), переписав его как общий метод.
Почему бы и нет? Сначала немного фона. Работа генераторов С# заключается в том, что компилятор компилирует ваш общий метод, который имеет сменные параметры, а затем заменяет их во время выполнения фактическими параметрами. Это вы уже знали.
Но параметризованная версия метода больше не знает о типах переменных, чем вы и я во время компиляции. В этом случае весь компилятор знает о Foo2
в том, что list
является IList<int>
. Мы имеем ту же информацию в общем Foo2
, что мы делаем в не-generic Foo1
.
На самом деле, чтобы избежать раздувания кода, JIT-компилятор создает только одно экземпляр универсального метода для всех ссылочных типов. Вот документация Microsoft, описывающая эту подстановку и создание экземпляра:
Если клиент указывает ссылочный тип, то компилятор JIT заменяет общие параметры на сервере IL на Object и компилирует его в собственный код. Этот код будет использоваться в любом дополнительном запросе для ссылочного типа вместо параметра общего типа. Обратите внимание, что таким образом компилятор JIT использует только повторный код. Экземпляры по-прежнему распределяются в зависимости от их размера от управляемой кучи, и нет кастинга.
Это означает, что версия JIT-компилятора метода (для ссылочных типов) не относится к типу, но это не имеет значения, поскольку компилятор обеспечил всю безопасность типа во время компиляции. Но что более важно для вашего вопроса, нет возможности выполнить inlining и получить повышение производительности.
Изменить: Наконец, эмпирически, я только что сделал тест как Foo1
, так и Foo2
, и они дают одинаковые результаты производительности. Другими словами, Foo2
не не быстрее Foo1
.
Добавьте для сравнения "встроенную" версию Foo0
:
int Foo0(List<int> list)
{
int sum = 0;
for (int i = 0; i < list.Count; ++i)
sum += list[i];
return sum;
}
Вот сравнение производительности:
Foo0 = 1719
Foo1 = 7299
Foo2 = 7472
Foo0 = 1671
Foo1 = 7470
Foo2 = 7756
Итак, вы можете видеть, что Foo0
, который может быть встроен, значительно быстрее, чем два других. Вы также можете увидеть, что Foo2
немного медленнее, а не где-то рядом с Foo0
.