对于C# - For和For More






2.58/5 (27投票s)
刚开始用 C#?你是否怀念 `for i = 1 to 50` 这样的语法?别找了,C# 也能通过一种独特的方式扩展来实现。
引言
刚开始用 C#?你是否怀念 `for i = 1 to 50
` 这样的语法?别找了,C# 也能通过一种独特的方式扩展来实现。
为什么还需要 for 循环?
第一个问题是为什么?通常只有 C++ 和 C# 开发者才会问这个问题。C# 开发者喜欢他们的 `for
` 循环,他们说它优雅、灵活、健壮,而且不需要其他东西。我同意他们的一些观点。它很灵活。它很健壮。技术上来说,确实“不需要”其他东西,但“需要”一些其他东西。至于优雅,每当有开发者对我说某个东西很优雅时,我就会打寒颤……
C 风格的 `for
` 循环如下:
for ([initializers]; [expression]; [iterators])
计数从 1 到 5
for (int i = 1; i <= 5; i++)
Console.WriteLine(i);
}
这很好,而且肯定是可以扩展的。表达式甚至迭代器都可以使用各种复杂的求值器。但在 99% 的情况下,实现的是一个简单的 `int
x to y` 解决方案。C# 有 `foreach
`,所以 `for
` 循环的使用大大减少了。在仍然使用 `for
` 的情况下,它主要还是用于执行这个简单的计数功能。
对于这样一个简单的计数功能,`for
` 循环在语法上有些过度。问题在于这种语法是一个“roach point”(容易出错的点)。什么是“roach”?“roach”是指经常出现的 bug。而“roach point”是指“roach”滋生的地方。从其他语言转向 C# 的开发者非常容易在这方面写错。他们会写成 `i = 5` 或 `i < 5`,而不是 `i <= 5`。这是一个简单的错误,但非常普遍。另一个常见的错误是这个——让我们使用一个基于 0 的循环。从 0 到 4——快点——需要做哪些改动才能从 0 计数到 4?你需要 *认真* 考虑这个问题吗?如果需要,那么就存在“roach”的可能。
现在 C++ 开发者会立刻反驳我,说只有傻瓜才不理解这种语法。但这不是关于理解,而是关于犯错。我经常看到在 `for
` 循环上犯这种愚蠢的错误,导致 bug。虽然对于 C# 新手来说这种情况更多,但我见过最有经验的 C++ 开发者也犯过这种新手错误。**“Roach Point”**!现在我并不是主张消除人们喜爱的 C 风格 `for
`——我只是提供一个替代方案,以便 C 风格的 `for
` 只在它**需要**时使用,而这大约只占 1% 的时间。
如何实现 for?
C# 和 .NET 都具有出色的可扩展性——我对此永远感激不尽。利用这种可扩展性,可以轻松地让 C# 支持以下语法:
foreach (int i in new Counter(1, 5)) {
Console.WriteLine(i.ToString());
}
很酷吧?而且猜猜怎么着——你的团队不会再犯那些愚蠢的错误了。它甚至可以接受一个可选的第三个参数 `step`,允许以 1 以外的步长进行增量。我还没有实现反向计数,但这也很容易做到。
替代语法
有些用户反对使用 `new
` 关键字。技术上来说,这并没有什么问题,只是有些用户觉得它很烦人。通过使用 `static
` 方法,语法可以更改为:
foreach (int i in Counter.Loop(1, 5)) {
Console.WriteLine(i.ToString());
}
这两种语法都可以接受。就我个人而言,我选择继续使用 `new
` 操作符。事实上,这个类可以很容易地扩展以支持两者。
其他语言
现在让我们将它与其他语言中的 `for
` 循环进行比较:
Visual Basic
for i = 1 to 5
Delphi
for i := 1 to 5 do
Python
foreach(i in range(1, 5))
在我写这篇文章的时候,我并不知道 Python 的语法。但它非常相似。
缺点
它稍微慢一点。除非你有一个非常紧凑的循环,里面代码很少,否则没有可衡量的差异。如果它能消除一个让客户花费时间或金钱的 bug,那么我认为为了每个客户节省的时间而牺牲一点皮秒是值得的。如果我每天使用的软件能更可靠一些,我当然愿意放弃不少皮秒。请注意,我并不是说要让应用程序运行得更慢——而是这样的小改动在时间上不会被注意到。
它还需要更多的内存。但我见过太多程序员来找我说“看,我节省了 20 字节!”却忽略了他们的代码更复杂,程序已经 20MB,而且他们花了 4 个小时来做这件事。除非我们谈论的是一个大数组中的几个字节,是的,我绝对同意这几个字节是值得的。假设它的内存占用是 24 字节。在一个嵌套的循环中,一次有多少个循环处于活动状态?让我们想象你的代码深入到一系列方法调用中,总共有 10 个循环。10 x 24 = 240。你认为我会在乎我的 .NET 应用多用了 240 字节吗?我怀疑你会注意到为典型的 .NET Hello World 程序增加了 240 字节。
效率的损失相比之下微不足道。我们不必浪费更快的 CPU,但作为一个整体行业,我们应该专注于更**可靠**的软件。这 anyways 也是 .NET 的宗旨……初始化内存、内存检查、边界检查,所有这些我们本应该做多年的事情。
代码
代码非常简单。`for
` 循环是通过创建一个返回整数的迭代器来实现的。如果你想看另一个扩展 C# 的有趣方式的例子,你可以查看 更简单的数据库事务——扩展 using 语句以执行自动数据库事务。
public class Counter
{
// This enumerator is not thread safe or multi enumerator safe.
// But its usage pattern does not require such.
public class CounterEnumerator
{
private Counter _Counter;
internal CounterEnumerator(Counter aCounter)
{
_Counter = aCounter;
}
public bool MoveNext()
{
_Counter._Current += _Counter._Step;
return _Counter._Current <= _Counter._End;
}
public int Current
{
get { return _Counter._Current; }
}
}
private int _Begin;
private int _End;
private int _Step;
private int _Current;
public Counter(int aBegin, int aEnd) : this(aBegin, aEnd, 1)
{
}
public Counter(int aBegin, int aEnd, int aStep)
{
_Current = aBegin - 1;
_Begin = aBegin;
_End = aEnd;
_Step = aStep;
}
public CounterEnumerator GetEnumerator()
{
return new CounterEnumerator(this);
}
}
再多一点思考的食物
for (UInt32 i = mPageCount - 1; i >= 0; i--) {
}
这是我最近看到的一段代码。你注意到它有什么问题吗?很少有读者会。这一点在首次将其发布到开发者论坛时得到了证实,即使在我暗示有问题需要找出之后,也只有极少数人注意到了问题。上面的代码不会按作者预期运行,并且是我被派去修复的 bug 的来源。正确的代码是:
for (UInt32 i = mPageCount - 1; i != UInt32.MaxValue; i--) {
}