Страницы

Поиск по вопросам

среда, 29 января 2020 г.

Foreach и новый объект

#c_sharp


Вот к примеру такой код, он загружает картинки из файлов и считает максимальную ширину
и суммарную высоту:

List bitmapsList = new List();
foreach (string fn in fileNames)
  {
    Bitmap bmp = new Bitmap(fn);  // Интересует вот эта строка
    bitmapsList.Add(bmp);
    if (bmp.Width > maxWidth) maxWidth = bmp.Width;
    totalHeight += bmp.Height;
  }


Вопрос: как будет правильней, создавать объект Bitmap bmp в цикле, или объявить его
перед циклом? Что происходит с ним после прохода одной интерации цикла?
    


Ответы

Ответ 1



Упрощенно можно считать что переменные, объявленные внутри блока ограниченного { } существуют до тех пор, пока выполняется код этого блока. Как только управление передается за пределы блока, все локальные переменные блока удаляются. С объектами иначе. Объект существует до тех пор, пока к нему можно получить доступ по ссылке. Когда ни одной живой ссылки не осталось, объект может быть удален сборщиком мусора. В реальности механизм в обоих случаях сложнее, но для начального понимания этого достаточно. В вашем коде, переменные объявленные внутри цикла будут пересоздаваться на каждой итерации. Ссылку на Bitmap вы кладете в List объявленный за пределами цикла, поэтому с ним ни чего не произойдет. Создание объекта, происходит в момент вызова конструктора new Bitmap(fn), и оно разумеется должно быть внутри цикла. Что касается вопроса "как лучше?" - чем код понятнее, тем лучше, компилятор и Jit-компилятор в процессе работы все равно оптимизируют и изменят его так, как посчитают нужным и эффективным. Хотя надо отметить, что для языков на основе стековой машины, C# в их числе, присутствует рекомендация - объявлять переменную максимально близко к месту ее использования, т.е. если переменная используется только внутри итерации цикла и ее значение не участвует в последующих итерациях - объявляем ее внутри цикла, в противном случае вне цикла. Ну и потратьте день другой, на целенаправленное изучение/повторение основ языка, принципов работы с памятью и сборки мусора в .NET, это реально нужно, если конечно вы планируете развиваться дальше в этом направлении. Вот тут есть отличный список литературы.

Ответ 2



Можно сделать как душа желает, но JIT-компилятор при выполнении оптимизирует Ваш код. Скорее всего, будет создана одна переменная, на которую будет ссылаться код в цикле.

Ответ 3



Смотрите. Текст Bitmap bmp объявляет ссылку на объект типа Bitmap. На каждой итерации цикла вы конструируете новый объект, и ссылка начинает ссылаться на этот объект (в результате присваивания). Если ссылка объявлена за циклом, то при этом (начиная со второй итерации) ссылка больше не указывает на старый объект, оставшийся с последней итерации, и он становится разрешённым к сборке сборщиком мусора. Если ссылка объявлена внутри цикла, то она умирает в конце каждой итерации, и объект опять-таки становится доступным сборщику мусора. Технически, разница состоит в том, что при объявлении ссылки за циклом старое значение ещё доступно в начале цикла, а также последнее значение доступно после окончания цикла. Обычно такая доступность не нужна или даже вредна: вы можете забыть проинициализировать переменную, и будете работать со старым объектом вместо нового. Пример: Bitmap bmp = null; foreach (string fn in fileNames) { if (condition) bmp = new Bitmap(fn); // else вы работаете со старым объектом } В случае, если bmp объявлять внутри, такое не пропустит компилятор. Поэтому обычно рекомендуют объявлять переменные в самом вложенном блоке, где только можно, чтобы защититься от глупых ошибок. Уточнение: как верно подсказывает @Zufir в комментарии, ссылка на bmp сохраняется в bitmapsList, так что объект не будет съеден в этом случае сборщиком мусора. Но без этого сохранения объект был бы доступен для сборки.

Комментариев нет:

Отправить комментарий