Подтвердить что ты не робот

С# foreach против каждого из них

Какой из них вы предпочитаете?

foreach(var zombie in zombies)
{
    zombie.ShuffleTowardsSurvivors();
    zombie.EatNearbyBrains();
}

или

zombies.Each(zombie => {
    zombie.ShuffleTowardsSurvivors();
    zombie.EatNearbyBrains();
});
4b9b3361

Ответ 1

Вторая форма.

На мой взгляд, чем меньше языковых конструкций и ключевых слов вы должны использовать, тем лучше. С# имеет достаточно посторонних crud в нем, как есть.

Как правило, чем меньше вы должны печатать, тем лучше. Серьезно, как вы не могли бы использовать "var" в таких ситуациях? Конечно, если быть явным, это ваша единственная цель, вы все равно будете использовать венгерскую нотацию... у вас есть IDE, которая дает вам информацию о типе всякий раз, когда вы наводите верх... или, конечно, Ctrl + Q, если вы используете Resharper...

@T.E.D. Последствия производительности вызова делегата являются второстепенной задачей. Если вы делаете это, убедитесь, что тысячи терминов, запустите метку точек и посмотрите, не приемлемо ли это.

@Reed Copsey: нестандартно, если разработчик не может понять, что делает ".Each", тогда у вас больше проблем, хе. Взлом языка, чтобы сделать его приятнее, - одна из величайших радостей программирования.

Ответ 2

Первый. Это часть языка по какой-то причине.

Лично я использовал бы второй, функциональный подход к управлению потоком, если есть веские причины для этого, например, используя Parallel.ForEach в .NET 4. У него много недостатков, в том числе:

  • Это медленнее. Он собирается ввести вызов делегата для каждого элемента, так же, как вы сделали foreach (..) { myDelegate(); }
  • Это нестандартное, поэтому большинству разработчиков будет сложнее понять
  • Если вы закрываете какие-либо локальные жители, вы заставите компилятор сделать закрытие. Это может привести к возникновению странных проблем при включении потоков, а также добавит совершенно ненужную навороченность для сборки.

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

Ответ 3

Здесь вы делаете некоторые очень императивные вещи, такие как запись выражения, а не выражение (поскольку, по-видимому, метод Each не возвращает значение) и мутирующее состояние (которое можно только предполагать, что это делают методы, поскольку они также кажутся return no value), но вы пытаетесь передать их как "функциональное программирование", передав набор операторов в качестве делегата. Этот код едва ли мог быть дальше от идеалов и идиом функционального программирования, поэтому зачем пытаться скрыть его как таковой?

Насколько мне нравятся языки с несколькими парадигмами, такие как С#, я думаю, что их легче всего понять и поддерживать, когда парадигмы смешиваются на более высоком уровне (например, весь метод, написанный либо функциональным, либо императивным стилем), а не когда несколько парадигм смешиваются внутри одного оператора или выражения.

Если вы пишете императивный код, просто будьте честны и используйте цикл. Это нечего стыдиться. Императивный код не является изначально плохим.

Ответ 4

Версия lamda на самом деле не медленнее. Я просто сделал быстрый тест, и версия делегата примерно на 30% быстрее.

Вот код:

class Blah {
    public void DoStuff() {
    }
}

        List<Blah> blahs = new List<Blah>();
        DateTime start = DateTime.Now;

        for(int i = 0; i < 30000000; i++) {
            blahs.Add(new Blah());
        }

        TimeSpan elapsed = (DateTime.Now - start);
        Console.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "Allocation - {0:00}:{1:00}:{2:00}.{3:000}",
         elapsed.Hours,
         elapsed.Minutes,
         elapsed.Seconds,
         elapsed.Milliseconds));

        start = DateTime.Now;

        foreach(var bl in blahs) {
            bl.DoStuff();
        }

        elapsed = (DateTime.Now - start);
        Console.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "foreach - {0:00}:{1:00}:{2:00}.{3:000}",
         elapsed.Hours,
         elapsed.Minutes,
         elapsed.Seconds,
         elapsed.Milliseconds));

        start = DateTime.Now;

        blahs.ForEach(bl=>bl.DoStuff());

        elapsed = (DateTime.Now - start);
        Console.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "lambda - {0:00}:{1:00}:{2:00}.{3:000}",
         elapsed.Hours,
         elapsed.Minutes,
         elapsed.Seconds,
         elapsed.Milliseconds));

ОК, поэтому я провел больше тестов и вот результаты.

  • Порядок выполнения (forach, лямбда или лямбда, foreach) не имел большого значения, лямбда-версия была еще быстрее:

    foreach - 00:00:00.561
    lambda - 00:00:00.389
    
    lambda - 00:00:00.317
    foreach - 00:00:00.337
  • Разница в производительности намного меньше для массивов классов. Вот цифры для Blah [30000000]:

    lambda - 00:00:00.317 
    foreach - 00:00:00.337
  • Вот такой же тест, но Blah - это структура:

    Blah[] version 
    lambda - 00:00:00.676 
    foreach - 00:00:00.437 
    
    List version:
    lambda - 00:00:00.461
    foreach - 00:00:00.391
  • Оптимизированная сборка, Blah - это структура, использующая массив.

    lambda - 00:00:00.426
    foreach - 00:00:00.079

Вывод: Нет никакого единого ответа для выполнения foreach vs lambda. Ответ: Это зависит. Здесь - более научный тест для List<T>. Насколько я могу сказать, это чертовски эффективно. Если вы действительно заинтересованы в производительности, используйте цикл for(int i.... Для повторения нескольких тысяч записей клиентов (пример) это действительно не так важно.

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

Заключение # 2 T[] (где T - тип значения) foreach цикл примерно в 5 раз быстрее для этого теста в оптимизированной сборке. Это единственное существенное различие между сборкой Debug и Release. Итак, вы идете, для массивов типов значений используйте foreach, все остальное - это не имеет значения.

Ответ 5

Этот вопрос содержит полезное обсуждение, а также ссылку на сообщение в блоге MSDN по философским аспектам темы.

Ответ 6

Я думаю, что методы расширения классные, но я думаю, что break и редактирование и продолжение более прохладное.

Ответ 7

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


С тех пор, как меня спросили, я уточню. Реализация метода вполне может быть скомпилирована отдельно от кода, который его вызывает. Это означает, что компилятор не знает точно, сколько циклов он должен будет выполнить.

Если вы используете форму "foreach", тогда эта информация может быть доступна компилятору при создании кода для цикла (он также может быть недоступен, и в этом случае никакой разницы).

Например, если компилятор знает (из предыдущего кода в том же файле), что в списке содержится ровно 20 элементов, он может заменить весь цикл на 20 ссылок.

Однако, когда компилятор создает код для метода "Каждый" в исходном файле, он не знает, насколько велика будет список вызывавших абонентов. Он должен поддерживать любой размер. Самое лучшее, что он может сделать, это попытаться найти какую-то оптимальную разворачиваемость для своего процессора и добавить дополнительный код для его прокрутки и выполнить правильный цикл, если он слишком мал для разворачивания. Для типичной небольшой петли это может даже оказаться медленнее. Конечно, для маленьких петель вам все равно, если они не находятся внутри большой петли.

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

Ответ 8

Я тоже не предпочитаю, из-за того, что я считаю ненужным использование "var". Я бы написал это как:

foreach(Zombie zombie in zombies){
}

Но что касается функционального или foreach, для меня я наиболее определенно предпочитаю foreach, потому что, по-видимому, нет веской причины для последнего.