首先,我同意 goto 语句在很大程度上与现代编程语言中的高级构造无关,并且在有合适的替代品可用时不应使用。
我最近在重读 Steve McConnell 的 Code Complete 的原始版本,忘记了他对常见编码问题的建议。几年前我刚开始的时候就读过它,但我认为我没有意识到这个食谱有多么有用。编码问题如下:在执行循环时,您经常需要执行循环的一部分来初始化状态,然后用其他一些逻辑执行循环,并用相同的初始化逻辑结束每个循环。一个具体的例子是实现 String.Join(delimiter, array) 方法。
我想每个人对这个问题的第一反应就是这个。假设 append 方法被定义为将参数添加到您的返回值。
bool isFirst = true;
foreach (var element in array)
{
if (!isFirst)
{
append(delimiter);
}
else
{
isFirst = false;
}
append(element);
}
注意:对此的轻微优化是删除 else 并将其放在循环的末尾。赋值通常是单条指令,等效于 else,将基本块的数量减少 1,并增加主要部分的基本块大小。结果是在每个循环中执行一个条件来确定是否应该添加分隔符。
我还看到并使用了其他方法来处理这个常见的循环问题。您可以先在循环外执行初始元素代码,然后从第二个元素执行循环到结束。您还可以更改逻辑以始终附加元素,然后附加分隔符,一旦循环完成,您可以简单地删除您添加的最后一个分隔符。
后一种解决方案往往是我更喜欢的解决方案,只是因为它不会复制任何代码。如果初始化序列的逻辑发生变化,您不必记住在两个地方修复它。然而,它确实需要额外的“工作”来做某事然后撤消它,这至少会导致额外的 cpu 周期,并且在许多情况下,例如我们的 String.Join 示例也需要额外的内存。
当时我很兴奋读到这个结构
var enumerator = array.GetEnumerator();
if (enumerator.MoveNext())
{
goto start;
do {
append(delimiter);
start:
append(enumerator.Current);
} while (enumerator.MoveNext());
}
这样做的好处是您不会得到重复的代码,也不会得到额外的工作。您开始循环到执行第一个循环的一半,这就是您的初始化。您仅限于使用 do while 构造来模拟其他循环,但翻译很容易,阅读起来并不困难。
所以,现在的问题。我很高兴地尝试将它添加到我正在处理的一些代码中,但发现它不起作用。在 C、C++、Basic 中效果很好,但在 C# 中,您不能跳转到不是父作用域的不同词法作用域内的标签。我非常失望。所以我想知道,在 C# 中处理这个非常常见的编码问题(我主要在字符串生成中看到它)的最佳方法是什么?
也许更具体的要求:
- 不要重复代码
- 不要做不必要的工作
- 不要比其他代码慢 2 到 3 倍
- 可读
我认为可读性是我所说的食谱唯一可能会受到影响的事情。但是它在 C# 中不起作用,那么下一个最好的事情是什么?
* 编辑 * 因为一些讨论,我改变了我的表现标准。性能通常不是这里的限制因素,所以更正确的目标应该是不要不合理,不要成为有史以来最快的。
我不喜欢我建议的替代实现的原因是因为它们要么重复代码,这为更改一个部分而不是另一部分留出了空间,或者对于我通常选择的那个,它需要“撤消”操作,这需要额外的思考和时间来撤消事情你刚刚做的。特别是对于字符串操作,这通常会使您因一个错误或未能考虑空数组并试图撤消未发生的事情而打开关闭状态。