Мысли о 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 невелико, поэтому производительность не является проблемой, кто-нибудь возражает против нового стиля по сравнению с традиционным?
Ответы
Ответ 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
в этом случае! ;-)