Есть ли разница между этими двумя версиями кода?
foreach (var thing in things)
{
int i = thing.number;
// code using 'i'
// pay no attention to the uselessness of 'i'
}
int i;
foreach (var thing in things)
{
i = thing.number;
// code using 'i'
}
Или компилятору все равно? Когда я говорю о разнице, я имею в виду производительность и использование памяти. ... Или, в принципе, какая-то разница или эти два кода после компиляции оказываются одним и тем же?
c#
performance
memory
Alternatex
источник
источник
Ответы:
TL; DR - это эквивалентные примеры на уровне IL.
DotNetFiddle делает этот ответ довольно симпатичным, так как он позволяет увидеть итоговый IL.
Я использовал немного другой вариант вашей конструкции цикла, чтобы ускорить тестирование. Я использовал:
Вариация 1:
Вариация 2:
В обоих случаях скомпилированный вывод IL отображался одинаково.
Итак, чтобы ответить на ваш вопрос: компилятор оптимизирует объявление переменной и делает два варианта эквивалентными.
Насколько я понимаю, компилятор .NET IL перемещает все объявления переменных в начало функции, но я не смог найти хороший источник, который четко заявил, что 2 . В этом конкретном примере вы видите, что он переместил их с этим утверждением:
При этом мы становимся слишком одержимыми в сравнении ...
Случай А, все переменные перемещены вверх?
Чтобы углубиться в это, я протестировал следующую функцию:
Разница здесь в том , что мы объявляем либо
int i
или наstring j
основе сравнения. Опять же, компилятор перемещает все локальные переменные в верхнюю часть функции 2 с помощью:Мне показалось интересным отметить, что, хотя
int i
в этом примере и не будет объявлено, код для его поддержки все еще генерируется.Случай B: А что
foreach
вместоfor
?Было отмечено, что
foreach
поведение отличается от того,for
и я не проверял то, о чем спрашивали. Поэтому я вставил эти два раздела кода, чтобы сравнить полученный IL.int
объявление вне цикла:int
объявление внутри цикла:Результирующий IL с
foreach
петлей действительно отличался от IL, сгенерированного с помощьюfor
петли. В частности, блок инициализации и секция цикла изменились.foreach
Подход генерируется более локальных переменных , и требуется некоторое дополнительное разветвление. По сути, в первый раз он переходит к концу цикла, чтобы получить первую итерацию перечисления, а затем возвращается почти к вершине цикла, чтобы выполнить код цикла. Затем он продолжает проходить, как и следовало ожидать.Но за ветвящееся различие , вызванным использования
for
иforeach
конструкции, не была не разница в IL на основании которойint i
декларация была сделана. Таким образом, мы все еще находимся в двух подходах, являющихся эквивалентными.Случай C: А как насчет разных версий компилятора?
В комментарии, который был оставлен 1 , была ссылка на вопрос SO, касающийся предупреждения о доступе к переменной с помощью foreach и использования замыкания . Часть, которая действительно привлекла мое внимание в этом вопросе, заключалась в том, что, возможно, были различия в работе компилятора .NET 4.5 по сравнению с более ранними версиями компилятора.
И вот где сайт DotNetFiddler подвел меня - все, что у них было доступно, - это .NET 4.5 и версия компилятора Roslyn. Поэтому я создал локальный экземпляр Visual Studio и начал тестировать код. Чтобы убедиться, что я сравнивал одни и те же вещи, я сравнил локально созданный код в .NET 4.5 с кодом DotNetFiddler.
Единственное отличие, которое я заметил, было в локальном блоке инициализации и объявлении переменной. Локальный компилятор был немного более конкретен в именовании переменных.
Но с этой незначительной разницей это было так хорошо, так хорошо. У меня был эквивалентный вывод IL между компилятором DotNetFiddler и тем, что производил мой локальный экземпляр VS.
Поэтому я перестроил проект, нацеленный на .NET 4, .NET 3.5, и, для хорошей оценки, на режим .NET 3.5 Release.
И во всех трех из этих дополнительных случаев сгенерированный IL был эквивалентен. Целевая версия .NET не влияла на IL, сгенерированный в этих примерах.
Подводя итог этому приключению: я думаю, что мы можем с уверенностью сказать, что компилятору не важно, где вы объявляете примитивный тип, и что это не влияет на память или производительность ни одним из методов объявления. И это справедливо независимо от использования цикла
for
илиforeach
.Я рассмотрел запуск еще одного случая, который включал замыкание внутри
foreach
цикла. Но вы спрашивали о влиянии того, где была объявлена переменная примитивного типа, поэтому я подумал, что я слишком далеко зашел над тем, о чем вы хотели спросить. На вопрос SO, который я упоминал ранее, есть отличный ответ, который дает хороший обзор эффектов замыкания на итерационные переменные foreach.1 Спасибо Энди за предоставленную оригинальную ссылку на вопрос SO, посвященный замыканиям внутри
foreach
циклов.2 Стоит отметить, что в спецификации ECMA-335 это рассматривается в разделе I.12.3.2.2 «Локальные переменные и аргументы». Я должен был увидеть получившийся IL, а затем прочитать раздел, чтобы понять, что происходит. Спасибо фанатам за то, что указал на это в чате.
источник
foreach
цикл, а также проверил целевую версию .NET.В зависимости от того, какой компилятор вы используете (я даже не знаю, имеет ли C # более одного), ваш код будет оптимизирован перед тем, как превратиться в программу. Хороший компилятор увидит, что вы каждый раз переинициализируете одну и ту же переменную с другим значением и эффективно управляете пространством памяти.
Если бы вы каждый раз инициализировали одну и ту же переменную постоянной, компилятор также инициализировал бы ее перед циклом и ссылался на нее.
Все зависит от того, насколько хорошо написан ваш компилятор, но для стандартов кодирования переменные всегда должны иметь как можно меньшую область видимости. Так что объявление внутри цикла - это то, чему меня всегда учили.
источник
во-первых, вы просто объявляете и инициализируете внутри цикла, поэтому каждый цикл времени повторяет инициализацию внутри цикла. Во-вторых, вы только заявляете вне цикла.
источник