Почему «while» так популярно в C #? - PullRequest
4 голосов
/ 18 апреля 2009

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

Мой настоящий вопрос заключается в следующем: я читаю много кода других людей на C # в эти дни, и я заметил, что одна конкретная форма итерации широко распространена (см. В коде).
Мой первый вопрос:

Все ли итерации эквивалентны?

И второе: почему предпочитаете первое? Это как-то связано с читабельностью? Теперь я не верю, что первая форма более читабельна, чем форма, как только вы к ней привыкнете, и читаемость - слишком субъективный элемент в этих конструкциях, и то, что вы используете чаще всего, будет казаться более читабельным но я могу заверить всех, что for-форма по крайней мере так же читабельна, поскольку в ней все в одной строке, и вы даже можете прочитать инициализацию в конструкции.

Таким образом, второй вопрос: почему третья форма видна гораздо меньше в коде?

        // the 'widespread' construct
        int nr = getNumber();
        while (NotZero(nr))
        { 
            Console.Write(1/nr);
            nr = getNumber();
        }

        // the somewhat shorter form
        int nr;
        while (NotZero(nr = getNumber()))           
            Console.Write(1 / nr);            

        // the for - form
        for (int nr = getNumber(); NotZero(nr); nr = getNumber())
            Console.Write(1 / nr);

Ответы [ 7 ]

5 голосов
/ 18 апреля 2009

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

string line;
while ( (line = reader.ReadLine()) != null)
...

и

int bytesRead;
while ( (bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
...

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

Если вам не нравится переменная, имеющая слишком большую область видимости, вы можете просто ввести дополнительный блок:

{
  int bytesRead;
  while ( (bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
  {
     // Body
  }
}

Лично я не склонен это делать - «слишком широкий» охват меня не беспокоит , что сильно.

Я подозреваю, что не составит труда написать метод для инкапсуляции всего этого. Что-то вроде:

ForEach(() => reader.ReadLine(), // Way to obtain a value
        line => line != null,    // Condition
        line =>
{
    // body
};

Имейте в виду, для чтения строк у меня есть класс, который помогает:

foreach (string line in new LineReader(file))
{
    // body
}

(Он не только работает с файлами - он довольно гибкий.)

5 голосов
/ 18 апреля 2009

Являются ли все эти итерации эквивалентными?

да

почему предпочитаете первое? Имеет это делать с читабельностью?

потому что вы можете расширить область действия nr var за пределы цикла while?

почему 3-я форма меньше видна в коде?

это эквивалентно, те же утверждения! Вы можете предпочесть последнее, потому что не хотите расширять область действия переменной nr

3 голосов
/ 18 апреля 2009

Я предлагаю другую альтернативу

    foreach (var x in InitInfinite(() => GetNumber()).TakeWhile(NotZero))
    {
        Console.WriteLine(1.0/x);
    }

где InitInfinite - тривиальная вспомогательная функция. Вся программа:

using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
    static IEnumerable<T> InitInfinite<T>(Func<T> f)
    {
        while (true)
        {
            yield return f();
        }
    }
    static int N = 5;
    static int GetNumber()
    {
        N--;
        return N;
    }
    static bool NotZero(int n) { return n != 0; }
    static void Main(string[] args)
    {
        foreach (var x in InitInfinite(() => GetNumber()).TakeWhile(NotZero))
        {
            Console.WriteLine(1.0/x);
        }
    }
}
3 голосов
/ 18 апреля 2009

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

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

Если отображение, фильтрация или сокращение неприменимы, я бы, возможно, написал небольшой макрос для цикла такого типа (хотя в C # таких нет, правда?).

2 голосов
/ 18 апреля 2009

Вот случайное предположение:

Когда я пишу код на C #, пишу только две циклические конструкции: while () и foreach (). То есть никто больше не использует «для», так как «foreach» часто работает и часто превосходит. (Это чрезмерная генерализация, но в ней есть доля правды.) В результате мой мозг должен напрягаться, чтобы прочитать любую петлю «для», потому что она незнакома.

2 голосов
/ 18 апреля 2009

Я думаю, что люди часто используют цикл while (), потому что он лучше всего представляет способ, которым вы визуализируете задачу в своей голове. Я думаю, что есть преимущества в производительности по сравнению с любой другой структурой цикла.

1 голос
/ 18 апреля 2009

Что касается того, почему (1) и (2) «предпочтительнее», чем (3), я чувствую, что большинство людей думают о последнем как о способе перебора диапазона, используя условие для определения диапазона, а не чем продолжать перебирать блок, пока какое-то условие все еще выполняется. Семантика ключевых слов поддается такой интерпретации, и я подозреваю, что отчасти из-за этого люди находят, что выражения наиболее читабельны в этом контексте. Например, я бы никогда не использовал (1) или (2) для итерации по диапазону, хотя я мог бы.

Между (1) и (2) я разрываюсь. Раньше я использовал (2) (в C) чаще всего из-за компактности, но сейчас (в C #) я обычно пишу (1). Я полагаю, что я стал ценить удобочитаемость, а не компактность, и (1) мне кажется, что легче анализировать быстро и, следовательно, более читабельно, даже если я повторяю небольшое количество логики.

Честно говоря, я редко пишу операторы while, как правило, используя foreach - или LINQ - в тех случаях, когда ранее использовались операторы while. Если подумать, я тоже не уверен, что использую многие для операторов, за исключением модульных тестов, где я генерирую некоторое фиксированное число тестового объекта.

...