В обоих языках выражения строятся при помощи применения операторов к именам и литералам. Условно можно считать, что имеется следующий общий набор операторов:
| x.y, f(x), a[x], new, x++, x-- | |
| +, -, !, ~, ++x, --x, (T)x | |
| *, /, % | левая |
| +, - | левая |
| <<, >> | левая |
| <, >, <=, >= | левая |
| ==, != | левая |
| & | левая |
| ^ | левая |
| | | левая |
| && | левая |
| || | левая |
| ?: | правая |
| =, *=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^= | правая |
Любой тип Java однозначно соответствует некоторому объекту класса java.lang.Class, любой метод описывается с помощью одного из объектов класса java.lang.reflect.Method, любое поле — с помощью одного из объектов класса java.lang.reflect.Field. Получить объект типа Class, представляющий тип T (даже если T = void), можно с помощью конструкции T.class. | В C# типы представляются объектами класса System.Type, методы — объектами System.Reflection.MethodInfo, а поля — объектами System.Reflection.FieldInfo. Объект типа System.Type, представляющий тип T, можно получить при помощи конструкции typeof(T). |
Для проверки того, что выражение x имеет тип T, в Java используется конструкция (x instanceof T), возвращающая значение логического типа. В обоих языках операция проверки типа имеет такой же приоритет, как операторы <, >, <=, >=. | Для проверки того, что выражение x имеет тип T, в C# используется конструкция (x is T), имеющая логический тип. Эта проверка использует естественные преобразования типов (подтипа в более общий тип или наоборот, если точный тип объекта является подтипом T) и автоупаковку/распаковку, не затрагивая определенных пользователем неявных преобразований. |
В C# имеется и другой оператор, связанный с преобразованием типа. Для преобразования объекта x к заданному ссылочному типу T можно использовать конструкцию (x as T), тип результата которой — T. Если в результате естественных преобразований типов и автоупаковки/распаковки, значение x не преобразуется к типу T, то результат этого выражения — null. Приоритет этого оператора такой же, как у оператора is, а ассоциативность — левая. | |
В Java есть дополнительный оператор сдвига числового значения вправо >>>, заполняющий освобождающиеся слева биты нулями. Он имеет такой же приоритет, как и остальные операторы сдвига, и левую ассоциативность. Соответствующий оператор присваивания >>>= имеет такой же приоритет, как и другие операторы присваивания, и правую ассоциативность. В C# можно строить выражения, в рамках которых переполнения при арифметических действиях вызывают (или не вызывают) исключения при помощи оператора checked(x) (unchecked(x)), где x — выражение, контекст вычисления которого мы хотим определить (см. раздел о целочисленных типах). Оба этих оператора входят в группу операторов с высшим приоритетом. | |
Выражение default(T) используется в C# 2.0 для получения значения типа T по умолчанию. Для ссылочных типов это null, для числовых типов — 0, для логического типа — false, a для остальных типов значений определяется на основе их структуры. Это выражение используется для инициализации данных в шаблонных типах, зависящих от типового параметра, который может быть как ссылочным типом, так и типом значений. Этот оператор входит в группу с высшим приоритетом. | |
Оператор ?? (null coalescing operator) используется в C# 2.0 в следующем смысле. Выражение (x??y) эквивалентно ((x == null)?y:x), только значение x вычисляется однократно, т.е., если значение x не равно null, то результатом этой операции является x, а иначе y. Этот оператор имеет приоритет меньший, чем приоритет условной дизъюнкции ||, но больший, чем приоритет условного оператора ?:. Он правоассоциативен. | |
В C# 2.0 введен дополнительный оператор :: для разрешения контекста имен в рамках глобального пространства имен или определенных синонимов. Дело в том, что в C# при разрешении имен, построенных с помощью точек, разделяющих идентификаторы, возможны многочисленные проблемы, связанные с непредвиденными модификациями библиотек. Например, если мы написали директиву using System.IO;, чтобы использовать класс FileStream с коротким именем, и одновременно определяем в этом контексте класс EmptyStream, то, если в будущем в System.IO появится класс EmptyStream, полученный код перестанет компилироваться. Эту ситуацию можно разрешить при помощи синонимов, определив, например, для System.IO синоним SIO, а для нашей собственной библиотеки, куда входит EmptyStream, синоним MIO, и используя имена классов только вместе с синонимами — SIO.FileStream, MIO.EmptyStream. Однако если в одной из используемых библиотек будет введено пространство имен MIO, проблема возникнет вновь. Чтобы однозначно отделить типы из внешних библиотек от внутрипрограммных, можно использовать оператор ::. При этом левый аргумент такого оператора может иметь два вида. Либо он имеет значение global — тогда имя, заданное правым аргументом, ищется в глобальном пространстве имен и конфликтует с внутренними именами. Либо он является именем синонима — тогда имя, заданное правым аргументом, ищется только в пространстве имен, определяемом этим синонимом. Этот оператор входит в группу с высшим приоритетом. | |
Тип или часть его операций, или даже отдельный блок в C# могут быть помечены модификатором unsafe. При этом содержимое помеченного типа, операции или блока попадает в небезопасный контекст (unsafe context). В рамках небезопасного контекста можно использовать указатели и операции над указателями, в частности, доступ к элементу данных по указателю с помощью оператора ->, построение указателей на данные и разыменование указателей, арифметические действия над указателями. В данном курсе мы не будем больше касаться правил написания небезопасного кода в C#, предоставляя заинтересованному читателю самому разобраться в них с помощью [8]. |