C# ++ 运算符在 foreach 循环中是否成为线程安全的?

Does C# ++ operator become threadsafe in foreach loop?

最近我从 VB 转到 C#,所以我经常使用 C# 到 VB.NET 的转换器来了解语法差异。 在将 next 方法移动到 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

C# 的 ++ 运算符替换为 System.Threading.Interlocked.Increment 这是否意味着如果在 foreach 循环中使用,非线程安全的 ++ 运算符将变为线程安全的?它是一种语法糖吗?如果那是真的,那么为什么转换器将 Interlocked.Increment 放在 VB 版本中?我认为 C# 和 VB 中的 foreach 工作完全相同。或者它只是一个转换器 "insurance"?

我相信这是因为您想在进行比较的同一语句中增加值。我对此没有太多的理由,但它确实感觉像是正确的答案,因为 trueCnt += 1 不允许在同一行中进行比较。它当然与它是一个 foreach 无关,尝试在循环外添加相同的行,我几乎可以肯定它也会转换为增量。 VB.Net 中根本没有其他语法可以在同一行中进行递增和比较。

我确定这只是一个转换器 hack,我想我可以解释这背后的原因。

但首先,为了回答您的问题,C# 中的内置 ++ 运算符不是线程安全的。它只是以下过程的语法糖(在 ++i 的情况下):

  • 读取i
  • 的值
  • 增加它
  • 写回i
  • return增量值

由于读写是分开的,所以是非原子操作。

现在,在 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无法使用inside VB.

中的表达式

除了上述问题之外,C# 的默认行为遵循 Java 不幸的整数溢出行为。尽管有时包装整数溢出语义很有用,但 C# 通常不会区分整数应在溢出时包装的情况与整数不应包装但程序员认为溢出陷阱不值得的情况。这可能会混淆转换工作,因为 VB.NET 使捕获行为比包装行为更容易和更快,而 C# 则相反。因此,出于速度原因翻译使用未检查数学的代码的逻辑方法是在 VB.NET 中使用普通检查数学,而翻译需要包装行为的代码的逻辑方法是在 VB.NET。 Threading.Interlocked.IncrementThreading.Increment.Add 方法使用包装整数行为,因此虽然从速度的角度来看它们不是最佳的,但它们很方便。