Оператор С# ++ становится потокобезопасным в цикле foreach?

Недавно я перешел с VB на С#, поэтому часто использовал конвертер С# в VB.NET, чтобы понять различия в синтаксисе. При перемещении следующего метода к VB я заметил интересную вещь.

исходный код С#:

 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 Означает ли это, что не потокобезопасный оператор ++ становится поточным, если используется в цикле foreach? Это своего рода синтаксический сахар? Если это так, то почему конвертер разместил Interlocked.Increment в версии VB? Я думал, что foreach как в С#, так и в VB работает точно так же. Или это просто конвертер "страхование"?

Ответ 1

Я уверен, что это просто взлом конвертера, и я думаю, что могу объяснить причины этого.

Но сначала, чтобы ответить на ваш вопрос, встроенный оператор ++ в С# не является потокобезопасным. Это просто синтаксический сахар для следующего процесса (в случае ++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.

Ответ 2

Я верю, потому что вы хотите увеличить значение в том же самом выражении, что и при сравнении. У меня нет слишком большого оправдания для этого, но он уверен, что правильный ответ, поскольку trueCnt + = 1 не позволяет сравнивать в одной строке. Это, безусловно, не имеет никакого отношения к тому, что это будет foreach, попробуйте добавить ту же линию за пределы цикла, и я почти уверен, что она также будет конвертироваться в Increment. Просто нет другого синтаксиса в VB.Net для увеличения и сравнения в одной строке.

Ответ 3

В дополнение к вышеупомянутым проблемам поведение по умолчанию для С# по умолчанию происходит по причине неудачного поведения целочисленного переполнения Java. Хотя времена, когда обертывание семантики over-overflow полезны, С# обычно не делает различий между ситуациями, когда целые числа должны обертываться при переполнении по сравнению с теми, где целые числа не предполагается обертывать, но программист не считает, что перехват переполнения стоит. Это может привести к запутыванию конверсий, поскольку VB.NET упрощает и ускоряет поведение захвата, чем поведение оболочки, в то время как С# делает обратное. Следовательно, логичным способом перевода кода, который использует непроверенную математику по причинам скорости, было бы использование обычной проверенной математики в VB.NET, в то время как логичным способом перевода кода, который требует обертывания, было бы использование методов целочисленного округления в VB.NET, Методы Threading.Interlocked.Increment и Threading.Increment.Add используют поведение целочисленного числа, поэтому, хотя они не оптимальны с точки зрения скорости, они удобны.