Лучше объявить переменную, используемую в цикле за пределами цикла скорее затем внутри? Иногда я вижу примеры, где переменная объявляется в цикле. Это эффективно заставляет программу выделять память для новой переменной каждый раз выполнения цикла? Или.NET, достаточно умная, чтобы знать, что это - действительно та же переменная.
Например, см. код ниже из этого ответа.
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[32768];
while (true)
{
int read = input.Read (buffer, 0, buffer.Length);
if (read <= 0)
return;
output.Write (buffer, 0, read);
}
}
Эта измененная версия больше была бы efficent?
public static void CopyStream(Stream input, Stream output)
{
int read; //OUTSIDE LOOP
byte[] buffer = new byte[32768];
while (true)
{
read = input.Read (buffer, 0, buffer.Length);
if (read <= 0)
return;
output.Write (buffer, 0, read);
}
}
Нет, это не будет более эффективным. Однако я бы переписал его так, чтобы объявить его вне цикла:
byte[] buffer = new byte[32768];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
Я не очень люблю использовать побочные эффекты в условиях, но по сути метод Read
дает вам два бита данных: достигли ли вы конца потока, и сколько вы прочитали. Цикл while теперь говорит: "Пока нам удалось прочитать некоторые данные... скопируйте их".
Это немного похоже на использование int.TryParse
:
if (int.TryParse(text, out value))
{
// Use value
}
Опять вы используете побочный эффект вызова метода в условии. Как я уже сказал, у меня нет привычки делать это кроме этого конкретного паттерна, когда вы имеете дело с методом, возвращающим два бита данных.
То же самое происходит при чтении строк из TextReader
:
string line;
while ((line = reader.ReadLine()) != null)
{
...
}
Возвращаясь к вашему первоначальному вопросу: если переменная будет инициализироваться на каждой итерации цикла и она будет использоваться только в теле цикла, я бы почти всегда объявлял ее внутри цикла. Небольшим исключением здесь является случай, когда переменная захватывается анонимной функцией - в этом случае поведение переменной будет отличаться, и я бы выбрал ту форму, которая обеспечит желаемое поведение... но в любом случае это почти всегда форма "объявлять внутри".
EDIT: Если говорить о масштабировании, то приведенный выше код действительно оставляет переменную в большей области видимости, чем нужно... но я считаю, что это делает цикл более понятным. Вы всегда можете решить эту проблему, введя новую область видимости, если хотите:
{
int read;
while (...)
{
}
}
В маловероятной ситуации, когда вам это не поможет, это все равно будет микрооптимизацией. Такие факторы, как ясность и правильное определение границ, гораздо важнее, чем крайний случай, когда это может практически ничего не изменить.
Вы должны дать своим переменным правильную область видимости, не думая о производительности. Конечно, сложные инициализации - это совсем другой зверь, поэтому если что-то должно быть инициализировано только один раз, но используется только в цикле, вы все равно захотите объявить это снаружи.
Как и в случае с множеством простых оптимизаций, подобных этой, компилятор позаботится об этом за вас. Если вы попробуете оба этих варианта и посмотрите на IL сборки в ildasm, то увидите, что они оба объявляют одну переменную int32 read, хотя он и меняет порядок объявлений:
.locals init ([0] int32 read,
[1] uint8[] buffer,
[2] bool CS$4$0000)
.locals init ([0] uint8[] buffer,
[1] int32 read,
[2] bool CS$4$0000)
Это действительно не имеет значения, и если бы я просматривал код для этого конкретного примера, мне было бы все равно.
Однако имейте в виду, что эти два значения могут означать очень разные вещи, если вы в конечном итоге захватите переменную read в замыкании.
См. Этот отличный пост Эрика Липперта, в котором возникает эта проблема относительно циклов foreach - http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the- петля-переменная-считается-вредна.aspx
Я соглашусь с большинством этих других ответов с оговоркой.
Если вы используете лямбда-выражения, вы должны быть осторожны с захватом переменных.
static void Main(string[] args)
{
var a = Enumerable.Range(1, 3);
var b = a.GetEnumerator();
int x;
while(b.MoveNext())
{
x = b.Current;
Task.Factory.StartNew(() => Console.WriteLine(x));
}
Console.ReadLine();
}
даст результат
3
3
3
, где
static void Main(string[] args)
{
var a = Enumerable.Range(1, 3);
var b = a.GetEnumerator();
while(b.MoveNext())
{
int x = b.Current;
Task.Factory.StartNew(() => Console.WriteLine(x));
}
Console.ReadLine();
}
даст результат
1
2
3
или какой-то там порядок. Это потому, что когда задача, наконец, запускается, она проверяет текущее значение ссылки на x. в первом примере все 3 цикла указывали на одну и ту же ссылку, тогда как во втором примере все они указывали на разные ссылки.
Я обычно предпочитаю последнее по личной привычке, потому что, даже если .NET достаточно умен, другие среды, в которых я мог бы работать позже, могут оказаться недостаточно умными. Это может быть не что иное, как компиляция в дополнительную строку кода внутри цикла для повторной инициализации переменной, но это все еще накладные расходы.
Даже если они идентичны для всех измеримых целей в любом данном примере, я бы сказал, что у последнего меньше шансов вызвать проблемы в долгосрочной перспективе.