Лучше ли объявлять переменную внутри или снаружи цикла?

Лучше сделать:

variable1Type foo; 
variable2Type baa; 

foreach(var val in list) 
{
    foo = new Foo( ... ); 
    foo.x = FormatValue(val); 

    baa = new Baa(); 
    baa.main = foo; 
    baa.Do();
}

Или:

foreach(var val in list) 
{
    variable1Type foo = new Foo( ... ); 
    foo.x = FormatValue(val); 

    variable2Type baa = new Baa(); 
    baa.main = foo; 
    baa.Do();
}

Вопрос: что быстрее 1 случай или 2 случая? Разница незначительна? Это то же самое в реальных приложениях? Это может быть оптимизация-микро, но я действительно хочу знать, что лучше.

Ответ 1

Эффективно, попробуйте конкретные примеры:

public void Method1()
{
  foreach(int i in Enumerable.Range(0, 10))
  {
    int x = i * i;
    StringBuilder sb = new StringBuilder();
    sb.Append(x);
    Console.WriteLine(sb);
  }
}
public void Method2()
{
  int x;
  StringBuilder sb;
  foreach(int i in Enumerable.Range(0, 10))
  {
    x = i * i;
    sb = new StringBuilder();
    sb.Append(x);
    Console.WriteLine(sb);
  }
}

Я сознательно выбрал тип значения и ссылочный тип в случае, когда это влияет на вещи. Теперь IL для них:

.method public hidebysig instance void Method1() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] int32 x,
        [2] class [mscorlib]System.Text.StringBuilder sb,
        [3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
    L_0000: ldc.i4.0 
    L_0001: ldc.i4.s 10
    L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
    L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
    L_000d: stloc.3 
    L_000e: br.s L_002f
    L_0010: ldloc.3 
    L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
    L_0016: stloc.0 
    L_0017: ldloc.0 
    L_0018: ldloc.0 
    L_0019: mul 
    L_001a: stloc.1 
    L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0020: stloc.2 
    L_0021: ldloc.2 
    L_0022: ldloc.1 
    L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
    L_0028: pop 
    L_0029: ldloc.2 
    L_002a: call void [mscorlib]System.Console::WriteLine(object)
    L_002f: ldloc.3 
    L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    L_0035: brtrue.s L_0010
    L_0037: leave.s L_0043
    L_0039: ldloc.3 
    L_003a: brfalse.s L_0042
    L_003c: ldloc.3 
    L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0042: endfinally 
    L_0043: ret 
    .try L_000e to L_0039 finally handler L_0039 to L_0043
}

.method public hidebysig instance void Method2() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 x,
        [1] class [mscorlib]System.Text.StringBuilder sb,
        [2] int32 i,
        [3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
    L_0000: ldc.i4.0 
    L_0001: ldc.i4.s 10
    L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
    L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
    L_000d: stloc.3 
    L_000e: br.s L_002f
    L_0010: ldloc.3 
    L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
    L_0016: stloc.2 
    L_0017: ldloc.2 
    L_0018: ldloc.2 
    L_0019: mul 
    L_001a: stloc.0 
    L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0020: stloc.1 
    L_0021: ldloc.1 
    L_0022: ldloc.0 
    L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
    L_0028: pop 
    L_0029: ldloc.1 
    L_002a: call void [mscorlib]System.Console::WriteLine(object)
    L_002f: ldloc.3 
    L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    L_0035: brtrue.s L_0010
    L_0037: leave.s L_0043
    L_0039: ldloc.3 
    L_003a: brfalse.s L_0042
    L_003c: ldloc.3 
    L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0042: endfinally 
    L_0043: ret 
    .try L_000e to L_0039 finally handler L_0039 to L_0043
}

Как вы можете видеть, помимо порядка в стеке, который был выбран компилятору, который мог бы быть совсем другим, это не имело никакого эффекта. В свою очередь, на самом деле нет ничего, что дало бы джиттеру много использовать то, что другое не дает.

Кроме этого, существует одна разновидность разницы.

В моих Method1(), x и sb привязаны к foreach и не могут быть доступны либо намеренно, либо случайно вне его.

В моих Method2(), x и sb во время компиляции не известно достоверно присвоить значение в foreach (компилятор не знает, что foreach будет выполнять хотя бы один петля), поэтому использование его запрещено.

Пока нет никакой реальной разницы.

Однако я могу назначить и использовать x и/или sb вне foreach. Как правило, я бы сказал, что это, вероятно, плохое наблюдение в большинстве случаев, поэтому я бы предпочел Method1, но у меня могла бы быть разумная причина хотеть обратиться к ним (более реалистично, если бы они не были неназначены), и в этом случае я бы пошел на Method2.

Тем не менее, это вопрос о том, как каждый код может быть расширен или нет, а не разница в коде, как написано. Действительно, нет никакой разницы.

Ответ 2

Это не имеет значения, оно не влияет на производительность вообще.

но я действительно хочу знать, как сделать правильный путь.

Большинство из вас скажут, что внутри-цикл имеет наибольший смысл.

Ответ 3

Это просто вопрос. В этом случае, когда foo и baa используются только в цикле for, лучше всего объявить их внутри цикла. Это также безопасно.

Ответ 4

Хорошо, я ответил на это, не заметив, где оригинальный плакат создавал новый объект каждый раз, проходя через цикл, а не просто "используя" объект. Таким образом, нет, на самом деле не должно быть незначительной разницы, когда дело доходит до производительности. При этом я бы пошел со вторым методом и объявил объект в цикле. Таким образом, он будет очищен во время следующего прохождения GC и будет поддерживать объект в пределах области.

--- Я оставлю свой ответ orignal, только потому, что я набрал все это, и это может помочь кому-то еще, кто будет искать это сообщение позже. Я обещаю, что в будущем я обращу больше внимания, прежде чем попытаюсь ответить на следующий вопрос.

Тим

На самом деле, я думаю, что есть разница. Если я правильно помню, каждый раз, когда вы создаете новый объект = new foo(), вы получите этот объект, добавленный в кучу памяти. Таким образом, создавая объекты в цикле, вы собираетесь добавлять накладные расходы системы. Если вы знаете, что цикл будет небольшим, это не проблема.

Итак, если вы закончите цикл с 1000 объектами в нем, вы будете создавать 1000 переменных, которые не будут удаляться до следующей коллекции мусора. Теперь нажмите на базу данных, где вы хотите что-то сделать, и у вас есть 20 000 строк для работы... Вы можете создать довольно системный запрос в зависимости от того, какой тип объекта вы создаете.

Это должно быть легко проверить... Создайте приложение, которое создает 10 000 элементов с отметкой времени при вводе цикла и при выходе. В первый раз, когда вы это сделаете, объявите переменную перед циклом и в следующий раз во время цикла. Возможно, вам придется столкнуться с тем, что подсчитайте гораздо выше 10K, чтобы увидеть реальную разницу в скорости.

Кроме того, существует проблема с областью. Если он создан в цикле, он исчезнет после выхода из цикла, так что вы не сможете получить к нему доступ снова. Но это также помогает с очисткой, так как сбор мусора в конечном итоге удалит его после выхода из цикла.

Тим

Ответ 5

Оба полностью действительны, но не уверены, что это "правильный путь" для этого.

Ваш первый случай более эффективен с точки зрения памяти (по крайней мере, в краткосрочной перспективе). Объявление переменных внутри цикла приведет к большему перераспределению памяти; однако с сборщиком мусора .NETs, ​​поскольку эти переменные выходят из сферы действия, они будут периодически очищаться, но не обязательно немедленно. Разница в скорости, возможно, незначительна.

Второй случай действительно более безопасен, так как ограничение объема ваших переменных в максимально возможной степени обычно является хорошей практикой.

Ответ 6

В JS распределение памяти является целым каждый раз. В С# обычно нет такой разницы, но если локальная переменная захватывается анонимным методом, например lambda expression, это имеет значение.