Почему было утверждение (j ++); запрещено?

169

Следующий код неверен (см. Его на ideone ):

public class Test
{
    public static void Main()
    {
        int j = 5;
        (j++);      // if we remove the "(" and ")" then this compiles fine.
    }
}

Ошибка CS0201: в качестве оператора могут использоваться только выражения присваивания, вызова, приращения, уменьшения, ожидания и новых объектов

  1. Почему код компилируется, когда мы удаляем скобки?
  2. Почему он не компилируется с круглыми скобками?
  3. Почему C # был разработан таким образом?
user10607
источник
29
@ Очень много людей, связанных с дизайном языка C #, работают на SO, поэтому я и спросил. Что-то не так с этим?
user10607 22.12.15
34
Я не могу представить более тематический вопрос, чем "почему конкретный язык программирования обрабатывает такое специфическое поведение таким образом?"
Маг Xy
20
Теперь у нас есть ответ Эрика Липперта. Может быть, вы хотите пересмотреть свое решение о принятом ответе. Это Рождество, так что, возможно, вы приняли ответ слишком рано.
Томас Веллер
17
@Servy Вы подразумеваете, что проектные решения никогда не документируются и абсолютно неизвестны всем остальным? Он не просит ТАК пользователей угадывать, он просит ответа - это не значит, что он просит угадать. Ответят ли люди с предположением на них , а не на него. И как OP указал, там есть люди на переполнение стека , которые сделали на самом деле работы на C # и из этих решений.
Роб
5
@Rob: Проблема в том, что, да, если обоснование уже где-то задокументировано, оно должно, но спекуляция - это весело, а кто не хочет делиться своим собственным? Если вы найдете способ убедиться, что такие чистые (или «образованные») догадки надежно отброшены, скажите мне, и мы заработаем целое состояние.
Дедупликатор

Ответы:

221

Глубокое понимание приветствуется.

Я сделаю все возможное.

Как отмечали другие ответы, здесь происходит то, что компилятор обнаруживает, что выражение используется в качестве оператора . Во многих языках - C, JavaScript и многих других - совершенно законно использовать выражение в качестве выражения. 2 + 2;является законным на этих языках, хотя это утверждение не имеет никакого эффекта. Некоторые выражения полезны только для их значений, некоторые выражения полезны только для их побочных эффектов (таких как вызов метода возврата void), а некоторые выражения, к сожалению, полезны для обоих. (Как приращение.)

Дело в том, что утверждения, которые состоят только из выражений, почти наверняка являются ошибками, если только эти выражения обычно не считаются более полезными для их побочных эффектов, чем их значения . Разработчики C # хотели найти золотую середину, позволяя выражениям, которые обычно считались побочными, не допускать выражений, которые обычно считаются полезными для их значений. Множество выражений, которые они идентифицировали в C # 1.0, были приращениями, декрементами, вызовами методов, присваиваниями и, что довольно противоречиво, вызовами конструктора.


В стороне: обычно думают, что конструкция объекта используется как значение, которое он производит, а не как побочный эффект конструкции; по моему мнению, new Foo();допуск - это немного неудача. В частности, я видел этот шаблон в реальном коде, который вызвал дефект безопасности:

catch(FooException ex) { new BarException(ex); } 

Может быть удивительно трудно обнаружить этот дефект, если код сложен.


Поэтому компилятор работает для обнаружения всех операторов, которые состоят из выражений, которых нет в этом списке. В частности, выражения в скобках идентифицируются как просто выражения в скобках. Их нет в списке «разрешено в качестве выражений операторов», поэтому они запрещены.

Все это служит принципу разработки языка C #. Если вы печатали, (x++);вы, вероятно, делали что-то не так . Это, вероятно, опечатка M(x++);или просто вещь. Помните, что отношение команды компилятора C # не « можем ли мы найти какой-то способ заставить эту работу? » Отношение команды C # - « если правдоподобный код выглядит как вероятная ошибка, давайте сообщим разработчику ». Разработчикам C # нравится такое отношение.

Теперь, все , что сказал, есть на самом деле несколько странных случаев , когда C # спецификация делает предполагающие или состояние напрямую , что круглые скобки запрещены , но C # компилятор позволяет им в любом случае. Почти во всех этих случаях незначительное несоответствие между указанным поведением и разрешенным поведением совершенно безвредно, поэтому авторы компилятора никогда не исправляли эти небольшие ошибки. Вы можете прочитать о них здесь:

Есть ли разница между return myVar и return (myVar)?

Эрик Липперт
источник
5
Re: «Обычно думают, что вызов конструктора используется для значения, которое он производит, а не для побочного эффекта конструкции; на мой взгляд, это немного ошибочно»: я мог бы представить, что один из факторов в этом решении было то , что если компилятор запретил его, не было бы никакой чистой исправить в тех случаях , когда будут призывающих его побочный эффект. (Большинство других запрещенных выражений имеют посторонние части, которые не имеют смысла и могут быть удалены, за исключением случаев ... ? ... : ..., когда исправление будет использоваться if/ elseвместо.)
ruakh
1
@ruakh: Поскольку это поведение, которое не следует поощрять, чистое исправление не требуется, если есть достаточно дешевое / простое исправление. Который есть; присвойте его переменной и не используйте эту переменную. Если вы хотите быть откровенным, что вы его не используете, предоставьте этой строке кода собственную область видимости. Хотя технически можно утверждать, что это меняет семантику кода (бесполезное присвоение ссылок все еще бесполезное присвоение ссылок), на практике это вряд ли имеет значение. Я признаю, что он генерирует немного другой IL ... но только если компилируется без оптимизации.
Брайан
Safari, Firefox и Chrome все дают ReferenceError при наборе текста contineu;.
Брайан Маккатон
2
@McBrainy: Ты прав. Я неправильно запоминаю дефект, который возникает из-за опечатки; Я проверю свои записи. В то же время я удалил оскорбительное заявление, так как оно не имеет отношения к большему вопросу здесь.
Эрик Липперт
1
@Mike: я согласен. Другая ситуация, в которой мы иногда видим это утверждение, похожа на контрольные примеры, try { new Foo(null); } catch (ArgumentNullException)...но очевидно, что эти ситуации по определению не являются рабочим кодом. Представляется разумным, что такой код может быть написан для присвоения фиктивной переменной.
Эрик Липперт
46

В спецификации языка C #

Выражения выражения используются для оценки выражений. Выражения, которые могут использоваться в качестве операторов, включают вызовы методов, выделения объектов с использованием оператора new, назначения с использованием = и составные операторы присваивания, операции увеличения и уменьшения с использованием операторов ++ и - и выражения await.

Помещение скобок в оператор создает новое так называемое выражение в скобках. Из спецификации:

Выражение в скобках состоит из выражения, заключенного в скобки. ... выражение в скобках оценивается путем вычисления выражения в скобках. Если выражение в скобках обозначает пространство имен или тип, возникает ошибка времени компиляции. В противном случае, результат выражения в скобках является результатом оценки содержащегося выражения.

Поскольку выражения в скобках не указаны в качестве допустимого выражения выражения, это не является допустимым выражением согласно спецификации. Почему дизайнеры решили сделать это таким образом, можно только догадываться, но моя ставка заключается в том, что круглые скобки не приносят никакой пользы, если все утверждение содержится в круглых скобках: stmtи (stmt)они точно такие же.

Фрэнк Брайс
источник
4
@Servy: Если вы внимательно прочитаете, это было немного более нюансом, чем это. Так называемый «оператор выражения» действительно является допустимым оператором, а в спецификации перечислены типы выражений, которые могут быть допустимыми. Однако я повторяю из спецификации, что тип выражения, называемого «выражением в скобках», НЕ находится в списке допустимых выражений, которые можно использовать в качестве допустимых выражений выражений.
Фрэнк Брайс
4
The OPничего не спрашивает о дизайне языка. Он просто хочет знать, почему это ошибка. Ответ: потому что это недопустимое утверждение .
Леандро
9
Ок мой плохой Ответ: потому что, согласно спецификации , это недопустимое утверждение. Вот вам и смысл : причина дизайна !
Леандро
3
Вопрос не в том, чтобы задаться глубокой, основанной на дизайне причиной того, почему язык был разработан для обработки этого синтаксиса таким образом - он спрашивает, почему один фрагмент кода компилируется, когда другой фрагмент кода (который для начинающих выглядит так, как он должен себя вести) идентично) не компилируется. Этот пост отвечает на это.
Маг Xy
4
@Servy JohnCarpenter не просто говорит: «Ты не можешь этого сделать». Он говорит, что эти две вещи, которые вас смутили, не одинаковы. j ++ - это выражение Expression, а (j ++) - выражение в скобках. Внезапно ОП теперь знает разницу и что они есть. Это хороший ответ. Он отвечает на его вопрос, а не на заголовок. Я думаю, что много споров происходит от слова «дизайн» в названии вопроса, но на это не должны отвечать дизайнеры, просто исходя из спецификаций.
думаю,
19

поскольку квадратные скобки i++создают / определяют выражение ... как говорится в сообщении об ошибке ... простое выражение не может быть использовано в качестве выражения.

почему язык был разработан таким образом? для предотвращения ошибок, имеющих вводящие в заблуждение выражения в виде операторов, которые не вызывают побочных эффектов, таких как наличие кода

int j = 5;
j+1; 

вторая строка не имеет никакого эффекта (но вы, возможно, не заметили). Но вместо того, чтобы компилятор удалял его (потому что код не нужен) .it явно просит вас удалить его (чтобы вы знали об ошибке) или ИЛИ исправить это, если вы забыли что-то напечатать.

редактировать :

чтобы сделать часть о bracked более понятной ... скобки в c # (помимо других применений, таких как приведение типов и вызов функций), используются для группировки выражений и возврата одного выражения (make из подвыражений).

на этом уровне кода разрешены только позиции .. так

j++; is a valid statement because it produces side effects

но с помощью пробела вы превращаете его в выражение

myTempExpression = (j++)

и это

myTempExpression;

недопустимо, потому что компилятор не может убедиться, что выражение является побочным эффектом (не без возникновения проблемы остановки).

CaldasGSM
источник
Да, я тоже так думал сначала. Но это действительно имеет побочный эффект. Он выполняет пост-приращение jпеременной, верно?
user10607 22.12.15
1
j++;является действительным утверждением, но, используя квадратные скобки, вы говорите let me take this stement and turn it into an expression... и выражения недействительны на тот момент кода
CaldasGSM
Очень жаль, что нет формы выражения «вычислять и игнорировать», поскольку в некоторых случаях код может захотеть утверждать, что значение может быть вычислено без выброса исключения, но не заботится о фактическом значении, вычисленном таким образом. Вычисление и игнорирование не будет использоваться слишком часто, но в таких случаях у программиста будет ясное намерение.
суперкат