关于使用Enumerable.Range与传统for循环的foreach的思考

Mar*_*the 64 .net c# for-loop c#-3.0

在C#3.0中,我喜欢这种风格:

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

在传统的for循环:

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

假设'n'很小,所以性能不是问题,是否有人反对传统风格的新风格?

mqp*_*mqp 52

我发现后者的"最小到最大"格式比Range为此目的的"最小计数" 格式更清晰.另外,我认为从这样的规范做出这样的改变并不是一个好习惯,这种改变不是更快,更短,不熟悉,也不是更明确.

也就是说,我并不反对这个想法.如果你对我的语法看起来像foreach (int x from 1 to 8)那样,我可能会同意这将是一个for循环的改进.但是,Enumerable.Range非常笨重.

  • 听起来像扩展方法的好例子1.to(8) (6认同)
  • 我喜欢你提出的语法! (3认同)
  • 就个人而言,这对我来说过于冗长和笨重(而不是for循环)但我明白这在某种程度上是一种品味问题. (3认同)

Luk*_*keH 39

这只是为了好玩.(我自己只使用标准的" 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--;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您也可以添加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);
}
Run Code Online (Sandbox Code Playgroud)

  • @unknown,Enumerable.Range只能向前计数,我的版本也倒数.你的代码也会产生一个不同的序列:尝试"foreach(在5.To(15)中的int i"). (2认同)

Mik*_*per 15

在C#6.0中使用

using static System.Linq.Enumerable;
Run Code Online (Sandbox Code Playgroud)

你可以简化它

foreach (var index in Range(1, 7))
{
    Console.WriteLine(index);
}
Run Code Online (Sandbox Code Playgroud)


spe*_*der 9

对于已经解决的问题,这似乎是一个漫长的啰嗦方法.这背后有一个完整的状态机Enumerable.Range并不是真正需要的.

传统格式是发展的基础,也是所有人都熟悉的.我觉得你的新风格没什么好处.

  • 在某些方面.当您想要从最小值迭代到最大值时,Enumerable.Range变得无法读取,因为第二个参数是一个范围,需要进行计算. (2认同)

THX*_*138 9

实际上,你可以做到这一点在C#中(通过提供ToDo作为扩展方法int,并IEnumerable<T>分别对应).

1.To(7).Do(Console.WriteLine);
Run Code Online (Sandbox Code Playgroud)

SmallTalk永远!

  • @BlueVoodoo它们是您需要定义的扩展方法. (2认同)

T.Y*_*cuk 7

正如 Nick Chapsas 在他出色的YouTube 视频中指出的那样,传统迭代和范围迭代之间没有显着的性能差异。即使基准测试也显示,少量迭代的情况下也存在纳秒级的差异。当循环变得相当大时,差异几乎消失了。

这是一种在范围循环中迭代其内容的优雅方法:

private static void Test()
{
    foreach (var i in 1..5)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

使用此扩展:

public static class Extension
{
    public static CustomIntEnumerator GetEnumerator(this Range range)
    {
        return new CustomIntEnumerator(range);
    }
    public static CustomIntEnumerator GetEnumerator(this int number)
    {
        return new CustomIntEnumerator(new Range(0, number));
    }
}

public ref struct CustomIntEnumerator
{
    private int _current;
    private readonly int _end;

    public CustomIntEnumerator(Range range)
    {
        if (range.End.IsFromEnd)
        {
            throw new NotSupportedException();
        }

        _current = range.Start.Value - 1;
        _end = range.End.Value;
    }

    public int Current => _current;

    public bool MoveNext()
    {
        _current++;
        return _current <= _end;
    }
}
Run Code Online (Sandbox Code Playgroud)

基准结果: 基准结果

我喜欢这种实施方式。但是,这种方法的最大问题是它无法在异步方法中使用它。


for*_*ran 6

我认为foreach + Enumerable.Range不太容易出错(你控制较少,错误的方法较少,比如减少体内的索引,所以循环永远不会结束等)

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

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


Tho*_*ker 6

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

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

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

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


Kac*_*che 6

我有点喜欢这个主意.它非常像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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它像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 ]


Bru*_*ell 6

今天如何使用新语法

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

using static Enumerizer;

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

<=不是和之间的区别<

我还在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();
}
Run Code Online (Sandbox Code Playgroud)


Tho*_*enz 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();
}
Run Code Online (Sandbox Code Playgroud)


mcN*_*Nux 5

我认为Range对于处理某些范围的内联非常有用:

var squares = Enumerable.Range(1, 7).Select(i => i * i);
Run Code Online (Sandbox Code Playgroud)

你们每个人都可以.需要转换为列表,但保持紧凑,这是你想要的.

Enumerable.Range(1, 7).ToList().ForEach(i => Console.WriteLine(i));
Run Code Online (Sandbox Code Playgroud)

但除了像这样的东西,我会使用传统的for循环.