4

问题字段有点太短,无法提出我的真正问题。如果有人可以更好地概括它,请随意。

我真正的问题是:这些天我正在阅读很多其他人的 C# 代码,我注意到一种特定形式的迭代被广泛传播,(见代码)。
我的第一个问题是:

所有这些迭代都是等价的吗?

我的第二个是:为什么更喜欢第一个?它与可读性有关吗?现在我不相信第一种形式比 for-form 更易读,一旦你习惯了它,可读性在这些结构中太过主观了,当然,你使用最多的东西看起来更具可读性,但我可以向大家保证,for-form 至少具有同样的可读性,因为它全部在一行中,您甚至可以阅读构造中的初始化。

因此第二个问题:为什么在代码中看到的第三种形式少得多?

        // 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);
4

7 回答 7

5

所有这些迭代都是等价的吗?

是的

为什么更喜欢第一个?有没有。与可读性有关?

因为您可能希望将 nr var 的范围扩展到 while 循环之外?

为什么在代码中看到的第 3 种形式少得多?

它是等效的,相同的陈述!您可能更喜欢后者,因为您不想扩展 nr 变量的范围

于 2009-04-18T10:15:16.823 回答
5

您显示的第一种和第三种形式重复了对 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
}

(它不仅适用于文件 - 它非常灵活。)

于 2009-04-18T10:20:28.667 回答
3

我认为第三种形式(for-loop)是这些替代方案中最好的,因为它将事物置于正确的范围内。另一方面,不得不重复调用 getNumber() 也有点尴尬。

一般来说,我认为显式循环被广泛过度使用。高级语言应该提供映射、过滤和归约。当这些高级构造适用且可用时,循环代替循环就像使用goto代替循环。

如果映射、过滤或缩减不适用,我可能会为这种循环编写一个小宏(不过,C# 没有这些,是吗?)。

于 2009-04-18T10:33:56.933 回答
3

我提供另一种选择

    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);
        }
    }
}
于 2009-04-18T10:59:23.893 回答
2

我认为人们经常使用 while() 循环,因为它最能代表您在脑海中可视化任务的方式。我认为使用它比任何其他循环结构都有任何性能优势。

于 2009-04-18T10:17:03.823 回答
2

这是一个随机的猜测:

当我编写 C# 代码时,我编写的仅有的两个循环结构是 while() 和 foreach()。也就是说,没有人再使用“for”了,因为“foreach”通常有效并且通常更优越。(这是一个过度概括,但它有一个真理的核心。)结果,我的大脑不得不紧张地阅读任何“for”循环,因为它不熟悉。

于 2009-04-18T10:20:30.337 回答
1

至于为什么(1)和(2)比(3)“优先”,我的感觉是,大多数人认为后者是一种迭代范围的方式,使用条件来定义范围,而不是继续在某些条件仍然成立时迭代一个块。关键字语义适合这种解释,我怀疑,部分原因是,人们发现表达式在这种情况下最易读。例如,我永远不会使用 (1) 或 (2) 来迭代一个范围,尽管我可以。

在(1)和(2)之间,我被撕裂了。由于紧凑性,我以前最常使用 (2)(在 C 中),但现在(在 C# 中)我通常写 (1)。我想我已经开始重视可读性而不是紧凑性,并且(1)似乎更容易快速解析,因此即使我最终重复了少量逻辑,我也更容易理解。

老实说,我很少再编写 while 语句了,通常在以前使用 while 语句的情况下使用 foreach 或 LINQ。想一想,我也不确定我是否使用了很多 for 语句,除非在单元测试中我生成一些固定数量的测试对象。

于 2009-04-18T12:17:18.510 回答