65.9K
CodeProject 正在变化。 阅读更多。
Home

对于C# - For和For More

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.58/5 (27投票s)

2005年5月6日

CPOL

5分钟阅读

viewsIcon

83688

downloadIcon

1

刚开始用 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--) {
}

 

C# - For 和 For More - CodeProject - 代码之家
© . All rights reserved.