最近我从VB转到了C#,所以我经常使用C#到VB.NET的转换器来理解语法差异。在将下一个方法移到VB时,我注意到了一件有趣的事情。
C#原始代码:
public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
{
int trueCnt = 0;
foreach(bool b in bools)
if (b && (++trueCnt > threshold))
return true;
return false;
}
VB.NET结果:
Public Function ExceedsThreshold(threshold As Integer, bools As IEnumerable(Of Boolean)) As Boolean
Dim trueCnt As Integer = 0
For Each b As Boolean In bools
If b AndAlso (System.Threading.Interlocked.Increment(trueCnt) > threshold) Then
Return True
End If
Next
Return False End Function
用System.Threading.Interlocked.Increment
替换C#的++
运算符如果在foreach
循环中使用,这是否意味着非线程安全的++
运算符将变为线程安全的?这是一种语法糖吗?若那个是真的,那个么转换器为什么把Interlocked.Increment
放在VB版本中呢?我认为foreach在C#和VB中的工作原理完全相同。或者它只是转换器的"保险"?
我确信这只是一个转换器破解,我想我可以解释这背后的原因。
但首先,为了回答您的问题,C#中内置的++
运算符不是线程安全的。它只是以下过程的语法糖(在++i
的情况下):
- 读取
i
的值 - 递增
- 将其写回
i
- 返回递增的值
由于存在单独的读写操作,因此这是一个非原子操作。
现在,在VB中没有++
运算符的直接等价物。最接近的是:
i += 1
但这是一个语句。相比之下,++i
是一个表达式。您可以在另一个语句或表达式中使用++i
,但不能在VB语句中使用。
使用Interlocked.Increment
只是一种轻松翻译代码的聪明方法,而不必将整个语句分解为多个其他语句。
如果没有这个技巧,转换器将不得不分解如下表达式:
if (b && (++trueCnt > threshold))
...
If b Then
trueCnt += 1
If trueCnt > threshold Then
...
End If
End If
正如你所看到的,这需要更多的重写。如果trueCnt
是一个属性,它甚至需要引入一个单独的临时变量(以避免对其求值两次)。
这需要比转换器使用的更简单的语法转换更深入的语义分析和控制流重写,因为trueCnt += 1
不能在VB.
我相信这是因为您希望在进行比较的同时增加同一语句中的值。我对此没有太多的理由,但这确实是正确的答案,因为trueCnt+=1不允许在同一行中进行比较。这当然与它是foreach无关,试着在循环外添加同一行,我几乎可以肯定它也会转换为Increment。VB.Net中根本没有其他语法可以在同一行中同时进行增量和比较。
除了上述问题外,C#的默认行为遵循Java不幸的整数溢出行为。尽管有时包装整数溢出语义很有用,但C#通常不会区分整数应该在溢出时包装的情况与整数不应该包装但程序员认为溢出捕获不值得的情况。这可能会混淆转换工作,因为VB.NET使捕获行为比包装行为更容易、更快,而C#则相反。因此,出于速度原因,翻译使用未检查数学的代码的逻辑方法是在VB.NET中使用普通的检查数学,而翻译需要包装行为的代码的合乎逻辑的方法是在VB.NET中使用包装整数方法。Threading.Interlocked.Increment
和Threading.Increment.Add
方法使用包装整数行为,因此,虽然从速度的角度来看它们不是最优的,但它们很方便。