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

Мысли о foreach с Enumerable.Range против традиционного для цикла

В С# 3.0 мне нравится этот стиль:

// Write the numbers 1 thru 7
foreach (int index in Enumerable.Range( 1, 7 ))
{
    Console.WriteLine(index);
}

по традиционному циклу for:

// Write the numbers 1 thru 7
for (int index = 1; index <= 7; index++)
{
    Console.WriteLine( index );
}

Если предположить, что n невелико, поэтому производительность не является проблемой, кто-нибудь возражает против нового стиля по сравнению с традиционным?

4b9b3361

Ответ 1

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

Тем не менее, я не против идеи в целом. Если бы вы подошли ко мне с синтаксисом, который выглядел примерно как foreach (int x from 1 to 8), я бы, наверное, согласился, что это будет улучшение по сравнению с циклом for. Однако Enumerable.Range довольно неуклюж.

Ответ 2

Это просто для удовольствия. (Я бы просто использовал стандартный формат цикла "for (int i = 1; i <= 10; i++)".)

foreach (int i in 1.To(10))
{
    Console.WriteLine(i);    // 1,2,3,4,5,6,7,8,9,10
}

// ...

public static IEnumerable<int> To(this int from, int to)
{
    if (from < to)
    {
        while (from <= to)
        {
            yield return from++;
        }
    }
    else
    {
        while (from >= to)
        {
            yield return from--;
        }
    }
}

Вы также можете добавить метод расширения Step:

foreach (int i in 5.To(-9).Step(2))
{
    Console.WriteLine(i);    // 5,3,1,-1,-3,-5,-7,-9
}

// ...

public static IEnumerable<T> Step<T>(this IEnumerable<T> source, int step)
{
    if (step == 0)
    {
        throw new ArgumentOutOfRangeException("step", "Param cannot be zero.");
    }

    return source.Where((x, i) => (i % step) == 0);
}

Ответ 3

В С# 6.0 с использованием

using static System.Linq.Enumerable;

вы можете упростить его до

foreach (var index in Range(1, 7))
{
    Console.WriteLine(index);
}

Ответ 4

Кажется, что это довольно долгий подход к проблеме, которая уже решена. За Enumerable.Range стоит целый конечный автомат, который на самом деле не нужен.

Традиционный формат имеет основополагающее значение для развития и знаком всем. Я не вижу никакого преимущества для вашего нового стиля.

Ответ 5

Фактически вы можете сделать это на С# (предоставляя To и Do как методы расширения на int и IEnumerable<T> соответственно):

1.To(7).Do(Console.WriteLine);

SmallTalk навсегда!

Ответ 6

Я думаю, что foreach + Enumerable.Range меньше подвержен ошибкам (у вас меньше контроля и меньше способов сделать это неправильно, например, уменьшение индекса внутри тела, чтобы цикл никогда не заканчивался и т.д.)

Проблема читаемости связана с семантикой функции диапазона, которая может изменяться с одного языка на другой (например, если задано только один параметр, оно будет начинаться с 0 или 1, или это конец включен или исключен или является вторым параметром count вместо этого конечное значение).

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

Ответ 7

Мне бы хотелось иметь синтаксис некоторых других языков, таких как Python, Haskell и т.д.

// Write the numbers 1 thru 7
foreach (int index in [1..7])
{
    Console.WriteLine(index);
}

К счастью, теперь у нас есть F # :)

Что касается С#, мне придется придерживаться метода Enumerable.Range.

Ответ 8

Мне нравится идея. Это очень похоже на Python. Вот моя версия в несколько строк:

static class Extensions
{
    public static IEnumerable<int> To(this int from, int to, int step = 1) {
        if (step == 0)
            throw new ArgumentOutOfRangeException("step", "step cannot be zero");
        // stop if next 'step' reaches or oversteps 'to', in either +/- direction
        while (!(step > 0 ^ from < to) && from != to) {
            yield return from;
            from += step;
        }
    }
}

Это работает как Python:

  • 0.To(4)[ 0, 1, 2, 3 ]
  • 4.To(0)[ 4, 3, 2, 1 ]
  • 4.To(4)[ ]
  • 7.To(-3, -3)[ 7, 4, 1, -2 ]

Ответ 9

@Luke: Я переопределил ваш метод расширения To() и использовал для этого метод Enumerable.Range(). Таким образом, он становится немного короче и использует как можно больше инфраструктуры, предоставленной нам .NET:

public static IEnumerable<int> To(this int from, int to)
{ 
    return from < to 
            ? Enumerable.Range(from, to - from + 1) 
            : Enumerable.Range(to, from - to + 1).Reverse();
}

Ответ 10

Я думаю, что Range полезен для работы с некоторым диапазоном inline:

var squares = Enumerable.Range(1, 7).Select(i => i * i);

Вы можете каждый раз. Требуется преобразование в список, но сохраняет вещи компактными, когда вы хотите.

Enumerable.Range(1, 7).ToList().ForEach(i => Console.WriteLine(i));

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

Ответ 11

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

Ответ 12

Я предполагаю, что могут быть сценарии, где Enumerable.Range(index, count) яснее при обработке выражений для параметров, особенно если некоторые из значений в этом выражении изменяются в цикле. В случае for выражение будет оцениваться на основе состояния после текущей итерации, тогда как Enumerable.Range() оценивается вверх.

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

Ответ 13

На мой взгляд, способ Enumerable.Range() является более декларативным. Новое и незнакомое людям? Конечно. Но я думаю, что этот декларативный подход дает те же преимущества, что и большинство других языковых возможностей LINQ.

Ответ 14

Я согласен с тем, что во многих (или даже в большинстве случаев) foreach гораздо более читабелен, чем стандартный for -loop, когда просто перебирает коллекцию. Тем не менее, ваш выбор использования Enumerable.Range(index, count) не является убедительным примером значения foreach для.

Для простого диапазона, начиная с 1, Enumerable.Range(index, count), выглядит вполне читабельно. Однако, если диапазон начинается с другого индекса, он становится менее читаемым, потому что вы должны правильно выполнить index + count - 1, чтобы определить, каким будет последний элемент. Например...

// Write the numbers 2 thru 8
foreach (var index in Enumerable.Range( 2, 7 ))
{
    Console.WriteLine(index);
}

В этом случае я предпочитаю второй пример.

// Write the numbers 2 thru 8
for (int index = 2; index <= 8; index++)
{
    Console.WriteLine(index);
}

Ответ 15

Строго говоря, вы неправильно используете перечисление.

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

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

Изменить. Я ошибаюсь. Как отметил Люк (см. Комментарии), можно надеяться на порядок при перечислении массива на С#. Это отличается от, например, использования "for in" для перечисления массива в Javascript.

Ответ 16

Как использовать новый синтаксис сегодня

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

using static Enumerizer;

// prints: 0 1 2 3 4 5 6 7 8 9
foreach (int i in 0 <= i < 10)
    Console.Write(i + " ");

Не разница между <= и <.

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

Минимальная и очень ограниченная реализация вышеприведенного цикла будет выглядеть примерно так:

public readonly struct Enumerizer
{
    public static readonly Enumerizer i = default;

    public Enumerizer(int start) =>
        Start = start;

    public readonly int Start;

    public static Enumerizer operator <(int start, Enumerizer _) =>
        new Enumerizer(start);

    public static Enumerizer operator >(int _, Enumerizer __) =>
        throw new NotImplementedException();

    public static IEnumerable<int> operator <=(Enumerizer start, int end)
    {
        for (int i = start.Start; i < end; i++)
            yield return i;
    }

    public static IEnumerable<int> operator >=(Enumerizer _, int __) =>
        throw new NotImplementedException();
}

Ответ 17

Мне нравится подход foreach + Enumerable.Range, и я иногда использую его.

// does anyone object to the new style over the traditional style?
foreach (var index in Enumerable.Range(1, 7))

Я возражаю против злоупотребления var в вашем предложении. Я ценю var, но, черт возьми, просто напишите int в этом случае! ;-)