Зарезервированные и контекстные ключевые слова

Многие языки программирования, включая C#, трактуют определённые цепочки символов как «особые».

Некоторые цепочки настолько особы, что не могут использоваться для идентификаторов. Давайте назовём их «зарезервированными ключевыми словами», а все остальные особые цепочки будем называть «контекстными ключевыми словами». Они «контекстные» потому, что цепочка может означать одно в контексте, где ожидается ключевое слово, и другое в контексте, где ожидается идентификатор.*

Спецификация C# определяет следующие зарезервированные слова:

abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void volatile while

Реализация дополнительно резервирует магические слова __arglist __makeref __reftype __refvalue, нужные для малоизвестных сценариев, о которых я, возможно, когда-нибудь напишу.

Это были слова, которые мы зарезервировали в C# 1.0; никаких новых зарезервированных слов с тех пор не добавилось. Бывает, что хочется, но мы всегда сдерживаемся. Если бы мы добавили новое зарезервированное слово, то любая программа, которая использовала его как идентификатор, сломалась бы при перекомпиляции. Да, при особом желании всегда можно применить ключевое слово как идентификатор: @typeof @goto = @for.@switch(@throw); это абсолютно легально, хотя довольно неестественно.

У нас также есть целая куча контекстных ключевых слов.

«Препроцессор» † использует все директивы (#define и другие), которые никогда не были валидными идентификаторами. Но он также использует контекстные ключевые слова hidden default disable restore checksum.

В C# 1.0 были контекстные ключевые слова get set value add remove.

В C# 2.0 добавились where partial global yield alias.

В C# 3.0 добавились join on equals into orderby ascending descending group by select let var.

В С# 4.0 добавится dynamic.

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

Например, при определении частичного класса, partial должно идти непосредственно перед class. Поскольку в C# 1.0 не могло быть корректных программ, в которых partial встречалось непосредственно перед class, мы знали, что добавление его в грамматику не сможет сломать никакую существующую программу.

Или вот еще пример. Рассмотрим var x = 1; – это могло быть корректной программой на C# 2.0, если бы там был объявлен тип под названием var с пользовательским оператором неявной конверсии из int. Семантический анализатор для операторов объявления проверяет, нет ли типа с названием var, доступного в месте объявления, и если такой есть, то применяются правила для обычного объявления. Только если такого типа нет, мы имеем право выполнить анализ для неявно типизированного объявления локальной переменной.

Быть может, кому-то интересно, почему мы добавили пять контекстных ключевых слов в C# 1.0, когда еще не было шанса сломать обратную совместимость. Почему бы не сделать get set value add remove «настоящими» ключевыми словами?

Потому что мы легко обошлись контекстными, а то, что реальные люди могли захотеть называть переменные или методы именами get, set, value, add или remove, казалось вполне вероятным.

Так что мы любезно оставили их незарезервированными.

Их легко было сделать контекстными, в отличие от, скажем, return. Его значительно труднее сделать контекстным ключевым словом, потому, что тогда return(10); приводило бы к неопределенности – это вызов метода с названием «return» или инструкция вернуть десять? Так что никакие другие зарезервиованные слова мы не стали делать контекстными.

*******

(*) Неприятным следствием такого определения является то, что using получается зарезервированным ключевым словом, несмотря на то, что его смысл зависит от контекста; значение определяется тем, начинает ли using директиву или оператор.

(†) Неудачный термин, поскольку «препроцессинг» не делается перед обычной компиляцией. В C#, так называемый «препроцессинг» происходит во время лексического анализа.

 

оригинал статьи