Меня беспокоит правильность, казалось бы, стандартного шаблона pre-С# 6 для запуска события:
EventHandler localCopy = SomeEvent;
if (localCopy != null)
localCopy(this, args);
Я прочитал Eric Lippert События и расы и знаю, что есть остальная проблема вызова устаревшего обработчика событий, но мое беспокойство является ли компилятор /JITter разрешено оптимизировать локальную копию, эффективно переписывая код как
if (SomeEvent != null)
SomeEvent(this, args);
с возможным NullReferenceException
.
В соответствии с Спецификацией языка С#, §3.10,
Критические точки выполнения, в которых должен сохраняться порядок этих побочных эффектов, являются ссылками на изменчивые поля (§10.5.3), операторы блокировки (§8.12) и создание и прекращение потоков.
- поэтому в указанном шаблоне нет критических точек выполнения, и оптимизатор не ограничен этим.
Связанный ответ Jon Skeet (год 2009) гласит
JIT не разрешает выполнять оптимизацию, о которой вы говорите в первой части, из-за условия. Я знаю, что это было создано как призрак некоторое время назад, но это недействительно. (Я проверил его с Джо Даффи или Вэнсом Моррисоном некоторое время назад, я не могу вспомнить, кто.)
- но комментарии относятся к этому сообщению в блоге (год 2008): События и темы (часть 4), в котором в основном говорится, что CLR 2.0 JITter (и, возможно, последующие версии?) Не должны вводить чтения или записи, поэтому в Microsoft.NET не должно быть никаких проблем. Но это ничего не говорит о других реализациях .NET.
[Боковое замечание: я не вижу, как не-введение чтения подтверждает правильность указанного шаблона. Невозможно ли JITTER увидеть какое-то устаревшее значение SomeEvent
в некоторой другой локальной переменной и оптимизировать одно из чтения, но не другое? Совершенно законный, верно?]
Кроме того, эта статья MSDN (год 2012): Модель памяти С# в теории и практике Игоря Островского гласит следующее:
Оптимизация без переопределения Некоторые оптимизации компилятора могут вводить или исключать определенные операции с памятью. Например, компилятор может заменить повторные чтения поля одним чтением. Аналогично, если код считывает поле и сохраняет значение в локальной переменной, а затем повторно читает переменную, компилятор может выбрать повторное чтение поля.
Поскольку спецификация ECMA С# не исключает оптимизацию без переопределения, предполагается, что они допустимы. Фактически, как обсуждают в Части 2, компилятор JIT выполняет эти типы оптимизации.
Это, похоже, противоречит ответу Джона Скита.
Поскольку теперь С# уже не является языком Windows, возникает вопрос, является ли справедливость шаблона следствием ограниченных оптимизаций JITter в текущей реализации CLR, или это свойство свойства языка.
Итак, вопрос следующий: является обсуждаемым шаблоном с точки зрения С# -языка? (подразумевается, требуется ли компилятор/время выполнения для запрета определенного типа оптимизаций.)
Конечно, нормативные ссылки на эту тему приветствуются.