80

在 C# 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”很小,所以性能不是问题,有人反对新风格而不是传统风格吗?

4

18 回答 18

59

为此,我发现后者的“最小到最大”格式比Range“最小计数”样式要清晰得多。另外,我不认为从不更快、不更短、不更熟悉和不明显更清晰的规范进行这样的改变真的不是一个好习惯。

也就是说,我一般不反对这个想法。如果您向我提出看起来像这样的语法,foreach (int x from 1 to 8)那么我可能会同意这将是对for循环的改进。但是,Enumerable.Range相当笨拙。

于 2009-05-27T14:00:53.817 回答
46

这只是为了好玩。(我自己只是使用标准的“ 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);
}
于 2009-05-27T15:24:34.647 回答
27

在 C# 6.0 中使用

using static System.Linq.Enumerable;

您可以将其简化为

foreach (var index in Range(1, 7))
{
    Console.WriteLine(index);
}
于 2016-04-13T07:27:42.810 回答
12

您实际上可以在 C# 中执行此操作(通过分别提供和To作为Do扩展方法):intIEnumerable<T>

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

SmallTalk 永远!

于 2009-05-27T15:25:13.023 回答
9

我有点喜欢这个主意。它非常像 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 ]
于 2012-10-10T10:07:17.390 回答
7

我认为 foreach + Enumerable.Range 不太容易出错(您的控制更少,出错的方法也更少,比如减少主体内的索引,这样循环就永远不会结束,等等)

可读性问题与 Range 函数语义有关,它可以从一种语言更改为另一种语言(例如,如果只给定一个参数,它将从 0 或 1 开始,或者包含或排除结束,或者第二个参数是计数而不是结束价值)。

关于性能,我认为编译器应该足够聪明以优化两个循环,以便它们以相似的速度执行,即使范围很大(我认为 Range 不会创建集合,但当然是迭代器)。

于 2009-05-27T14:13:00.563 回答
7

对于已经解决的问题,这似乎是一种相当冗长的方法。背后有一个Enumerable.Range不需要的完整状态机。

传统格式是开发的基础,并且为所有人所熟悉。我真的看不出你的新风格有什么好处。

于 2009-05-27T13:41:07.587 回答
6

我认为 Range 对于使用一些内联范围很有用:

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

你可以每一个过来。需要转换为列表,但在您想要的时候保持紧凑。

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

但除了这样的事情,我会使用传统的 for 循环。

于 2014-05-29T15:20:55.673 回答
6

我想要一些其他语言的语法,如 Python、Haskell 等。

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

幸运的是,我们现在得到了 F# :)

至于 C#,我将不得不坚持使用该Enumerable.Range方法。

于 2009-05-30T00:08:13.733 回答
5

@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();
}
于 2009-12-21T16:29:32.773 回答
4

今天如何使用新语法

由于这个问题,我尝试了一些东西来提出一个很好的语法,而无需等待一流的语言支持。这是我所拥有的:

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();
}
于 2019-09-03T17:39:49.450 回答
3

在我看来,这种Enumerable.Range()方式更具声明性。对人们来说是新的和不熟悉的?当然。但我认为这种声明性方法与大多数其他与 LINQ 相关的语言功能具有相同的好处。

于 2012-03-19T10:30:01.193 回答
3

我相信每个人都有自己的个人喜好(许多人更喜欢后者,因为它比几乎所有编程语言都熟悉),但我和你一样,开始越来越喜欢 foreach,尤其是现在你可以定义一个范围.

于 2009-05-27T13:39:57.667 回答
2

我同意在许多(甚至大多数情况下)在简单地迭代集合时foreach比标准循环更具可读性。for但是,您对使用的选择并不是over forEnumerable.Range(index, count)价值的一个强有力的例子。foreach

对于从1, Enumerable.Range(index, count)看起来很可读的简单范围。但是,如果范围以不同的索引开头,则它的可读性会降低,因为您必须正确执行index + count - 1以确定最后一个元素是什么。比如……</p>

// 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);
}
于 2009-05-27T14:08:56.470 回答
2

Enumerable.Range(index, count)我想在处理参数表达式时可能会有更清晰的场景,特别是如果该表达式中的某些值在循环内被更改。在表达式的情况下,for将根据当前迭代后的状态进行评估,而预先评估Enumerable.Range()

除此之外,我同意坚持for通常会更好(对更多人来说更熟悉/可读......可读性是需要维护的代码中的一个非常重要的值)。

于 2009-05-27T13:56:20.223 回答
1

我确实喜欢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在这种情况下写吧!;-)

于 2009-05-27T14:28:22.187 回答
1

严格来说,你滥用了枚举。

Enumerator 提供了逐一访问容器中所有对象的方法,但它不保证顺序。

可以使用枚举来查找数组中的最大数。如果您使用它来查找第一个非零元素,那么您将依赖于您不应该知道的实现细节。在您的示例中,订单似乎对您很重要。

编辑:我错了。正如 Luke 指出的(见评论),在 C# 中枚举数组时依赖顺序是安全的。这与例如在 Javascript 中使用“for in”枚举数组不同。

于 2009-05-27T16:26:01.513 回答
0

只是把我的帽子扔进戒指。

我定义这个...

namespace CustomRanges {

    public record IntRange(int From, int Thru, int step = 1) : IEnumerable<int> {

        public IEnumerator<int> GetEnumerator() {
            for (var i = From; i <= Thru; i += step)
                yield return i;
        }

        IEnumerator IEnumerable.GetEnumerator()
            => GetEnumerator();
    };

    public static class Definitions {

        public static IntRange FromTo(int from, int to, int step = 1)
            => new IntRange(from, to - 1, step);

        public static IntRange FromThru(int from, int thru, int step = 1)
            => new IntRange(from, thru, step);

        public static IntRange CountFrom(int from, int count)
            => new IntRange(from, from + count - 1);

        public static IntRange Count(int count)
            => new IntRange(0, count);

        // Add more to suit your needs. For instance, you could add in reversing ranges, etc.
    }
}

然后在我想使用它的任何地方,我将它添加到文件的顶部......

using static CustomRanges.Definitions;

并像这样使用它...

foreach(var index in FromTo(1, 4))
    Debug.WriteLine(index);
// Prints 1, 2, 3

foreach(var index in FromThru(1, 4))
    Debug.WriteLine(index);
// Prints 1, 2, 3, 4

foreach(var index in FromThru(2, 10, 2))
    Debug.WriteLine(index);
// Prints 2, 4, 6, 8, 10

foreach(var index in CountFrom(7, 4))
    Debug.WriteLine(index);
// Prints 7, 8, 9, 10

foreach(var index in Count(5))
    Debug.WriteLine(index);
// Prints 0, 1, 2, 3, 4

foreach(var _ in Count(4))
    Debug.WriteLine("A");
// Prints A, A, A, A

这种方法的好处在于名称,您可以确切地知道是否包含结尾。

于 2021-01-10T05:24:30.547 回答