Почему компилятор C# удаляет цепочку вызовов методов, когда последний является условным?
рассмотрим следующие классы:
public class A {
public B GetB() {
Console.WriteLine("GetB");
return new B();
}
}
public class B {
[System.Diagnostics.Conditional("DEBUG")]
public void Hello() {
Console.WriteLine("Hello");
}
}
теперь, если мы будем называть методы таким образом:
var a = new A();
var b = a.GetB();
b.Hello();
в сборке выпуска (т. е. нет DEBUG
флаг), мы видим только GetB
печатается на консоли, как вызов Hello()
будет пропущен компилятором. В отладочной сборке появятся обе печати.
теперь давайте цепочку вызовов методов:
a.GetB().Hello();
поведение в отладочной сборке остается неизменным; однако мы получаем другое если этот флаг не установлен: и вызовы опущены, и никакие отпечатки не появляются на консоли. Быстрый взгляд на IL показывает, что вся строка не была скомпилирована.
по словам самый последний стандарт ECMA для C# (ECMA-334, т. е. C# 5.0), ожидаемое поведение, когда Conditional
атрибут помещается на методе следующим образом (акцент мой):
вызов условного метода включается, если один или несколько связанных с ним условные символы компиляции-это определяется в точке вызова, в противном случае звонок пропущен. (§22.5.3)
это, кажется, не означает, что вся цепочка должна быть проигнорирована, поэтому мой вопрос. Это, как говорится,в C# 6.0 проект спецификации от Microsoft предлагает немного больше деталей:
если символ определен, вызов включен; в противном случае вызов (включая оценку приемника и параметров вызов) опущен.
тот факт, что параметры вызова не оцениваются, хорошо документирован, поскольку это одна из причин, по которой люди используют эту функцию, а не #if
директивы в теле функции. Часть об "оценке приемника", однако, является новой - я не могу найти ее в другом месте, и это, похоже, объясняет вышеупомянутое поведение.
в свете этого, мой вопрос:каково обоснование компилятора C# нет оценкаa.GetB()
в этой ситуации? должен ли он действительно вести себя по-другому в зависимости от того, хранится ли получатель условного вызова во временной переменной или нет?
3 ответа:
я немного покопался и нашел спецификация языка C# 5.0 на самом деле уже содержат вашу вторую цитату в разделе 17.4.2 условный атрибут на странице 424.
Марк Gravell это!--22--> уже показывает, что такое поведение предназначено и что оно означает на практике. Вы также спросили о обоснование за этим, но, похоже, недовольны упоминанием Марка об удалении накладных расходов.
может быть, вам интересно почему считается накладные расходы, которые могут быть удалены?
a.GetB().Hello();
не вызывается вообще в вашем случае сHello()
быть опущенным может показаться странным по номинальной стоимости.я не знаю обоснование этого решения, но я нашел некоторые правдоподобные рассуждения самостоятельно. Может быть, это и вам поможет.
способ сцепления возможно только в том случае, если каждый предыдущий метод имеет возвращаемое значение. Этот имеет смысл, когда вы хотите что-то сделать с этими значениями, т. е.
a.GetFoos().MakeBars().AnnounceBars();
если у вас есть функция, что только тут что-то без возврата значения вы не можете связать что-то за ним, но можете поместить его в конец цепочки методов, как в случае с вашим условным методом, поскольку он должен иметь тип возврата void.
также обратите внимание, что результат из предыдущих вызовов метода получает выбросить, так что в вашем пример
a.GetB().Hello();
ваш результатGetB()
нет причин жить после выполнения этого заявления. В принципе, вы означает вам нужен результатGetB()
только использоватьHello()
.если
Hello()
опускается, почему вам нужноGetB()
потом? Если вы опуститеHello()
ваша линия сводится кa.GetB();
без какого-либо назначения, и многие инструменты дают предупреждение, что вы не используете возвращаемое значение, потому что это редко то, что вы хотите делать.причина, почему вы, кажется, не в порядке с этим ваш метод не только пытается сделать то, что необходимо, чтобы вернуть определенное значение, но у вас также есть побочный эффект, а именно I / O. если бы у вас вместо этого было чистая функция там действительно нет причины
GetB()
если опустить последующий вызов, т. е. если вы не собираетесь ничего делать с результатом.если вы назначаете результат
GetB()
в переменная, это оператор сам по себе и будет выполняться в любом случае. Так что это рассуждение объясняет, почему вvar b = a.GetB(); b.Hello();
только вызов
Hello()
опущен, в то время как при использовании метода цепочки вся цепочка опущена.вы также можете посмотреть где-то совсем по-другому, чтобы получить лучшую перспективу:оператор или оператор Элвис
?
введено в C# 6.0. Хотя это только синтаксический сахар для более сложного выражение с нулевыми проверками позволяет построить что-то вроде цепочки методов с возможностью короткого замыкания на основе нулевой проверки.например.
GetFoos()?.MakeBars()?.AnnounceBars();
достигнет своего конца только в том случае, если предыдущие методы не вернутnull
, в противном случае последующие вызовы игнорируются.это может быть нелогичным, но попробуйте подумать о своем сценарии как обратном этому: компилятор пропускает ваши вызовы до
Hello()
в своемa.GetB().Hello();
цепь, так как вы не достигаете в любом случае конец цепи.
отказ от ответственности
это сводится к фразе:
(включая оценку приемника и параметров вызова) опущен.
в выражении:
a.GetB().Hello();
"оценка приемника":
a.GetB()
. Итак: это опущено согласно спецификации, а это полезный трюк, позволяющий[Conditional]
чтобы избежать накладных расходов для вещей, которые не используется. Когда вы кладете его в местные:var b = a.GetB(); b.Hello();
тогда "оценка приемника" - это просто локальная
b
, но оригиналvar b = a.GetB();
по-прежнему оценивается (даже если локальныйb
заканчивается удалением).этой можете имеют непреднамеренные последствия, так что: используйте
[Conditional]
С большой осторожностью. Но причины так что такие вещи, как ведение журнала и отладки могут быть легко добавлены и удалены. Обратите внимание, что параметры могут и быть проблематичным, если лечить наивно:LogStatus("added: " + engine.DoImportantStuff());
и:
var count = engine.DoImportantStuff(); LogStatus("added: " + count);
может быть очень различные Если
LogStatus
отмечается[Conditional]
- в результате чего ваши фактические "важные вещи" не были сделаны.
должен ли он действительно вести себя по-другому в зависимости от того, хранится ли получатель условного вызова во временной переменной или нет?
да.
каково обоснование того, что компилятор C# не оценивает
a.GetB()
в этой ситуации?ответы Марка и Серена в основном верны. Этот ответ просто четко документировать сроки.
- функция была разработана в 1999 году, и намерение функции всегда было удалить все заявление.
- в примечаниях к проекту от 2003 года указывается, что проектная группа поняла тогда, что спецификация была неясна по этому вопросу. До этого момента спецификация только вызывала это аргументы не будет оцениваться. Я отмечаю, что спецификация делает распространенную ошибку, называя аргументы "параметрами", хотя, конечно, можно предположить, что они означают "фактические параметры", а не " формальные параметры."
- рабочий элемент должен был быть создан, чтобы исправить спецификацию ECMA на этом этапе; по-видимому, этого никогда не было.
- первый раз, когда исправленный текст появляется в любой спецификации C#, была спецификация C# 4.0, которая, как я считаю, была 2010. (Я не помню, было ли это одним из моих исправлений, или кто-то еще нашел его.)
- если спецификация ECMA 2017 не содержит эту поправку, то это ошибка, которая должна быть исправлено в следующем выпуске. Лучше на 15 лет позже, чем никогда, я думаю.