рефераты конспекты курсовые дипломные лекции шпоры

Реферат Курсовая Конспект

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

C# Спецификация языка - раздел Программирование,     C# Спе ...

 

 

C#

Спецификация языка

Версия 3.0


Уведомление

© Корпорация Майкрософт (Microsoft Corp.), 1999-2007. Все права защищены.

Microsoft, Windows, Visual Basic, Visual C# и Visual C++ являются охраняемыми товарными знаками корпорации Майкрософт в США и других странах.

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


Оглавление

1. Введение.............................................................................................................................................. 1

1.1 Программа «Hello world»............................................................................................................... 1

1.2 Структура программы.................................................................................................................... 2

1.3 Типы и переменные......................................................................................................................... 4

1.4 Выражения....................................................................................................................................... 8

1.5 Операторы..................................................................................................................................... 10

1.6 Классы и объекты......................................................................................................................... 14

1.6.1 Члены...................................................................................................................................... 14

1.6.2 Доступность............................................................................................................................ 15

1.6.3 Параметры типа...................................................................................................................... 15

1.6.4 Базовые классы....................................................................................................................... 16

1.6.5 Поля........................................................................................................................................ 16

1.6.6 Методы.................................................................................................................................... 17

1.6.6.1 Параметры........................................................................................................................ 17

1.6.6.2 Тело метода и локальные переменные........................................................................... 19

1.6.6.3 Статические методы и методы экземпляра.................................................................... 19

1.6.6.4 Виртуальные, переопределяющие и абстрактные методы............................................ 20

1.6.6.5 Перегрузка метода........................................................................................................... 22

1.6.7 Другие функции-члены.......................................................................................................... 23

1.6.7.1 Конструкторы................................................................................................................... 25

1.6.7.2 Свойства........................................................................................................................... 26

1.6.7.3 Индексаторы..................................................................................................................... 26

1.6.7.4 События............................................................................................................................ 27

1.6.7.5 Операторы........................................................................................................................ 27

1.6.7.6 Деструкторы..................................................................................................................... 28

1.7 Структуры..................................................................................................................................... 28

1.8 Массивы......................................................................................................................................... 29

1.9 Интерфейсы................................................................................................................................... 31

1.10 Перечисляемые типы.................................................................................................................. 32

1.11 Делегаты...................................................................................................................................... 33

1.12 Атрибуты..................................................................................................................................... 34

2. Лексическая структура.................................................................................................................... 37

2.1 Программы.................................................................................................................................... 37

2.2 Грамматики................................................................................................................................... 37

2.2.1 Грамматическая нотация....................................................................................................... 37

2.2.2 Лексическая грамматика........................................................................................................ 38

2.2.3 Синтаксическая грамматика.................................................................................................. 38

2.3 Лексический анализ...................................................................................................................... 38

2.3.1 Признаки конца строки.......................................................................................................... 39

2.3.2 Комментарии.......................................................................................................................... 40

2.3.3 Пробел.................................................................................................................................... 41

2.4 Лексемы......................................................................................................................................... 41

2.4.1 Управляющие последовательности символов Юникода...................................................... 41

2.4.2 Идентификаторы.................................................................................................................... 42

2.4.3 Зарезервированные слова...................................................................................................... 44

2.4.4 Литералы................................................................................................................................. 44

2.4.4.1 Логические литералы....................................................................................................... 44

2.4.4.2 Целочисленные литералы................................................................................................ 45

2.4.4.3 Действительные литералы............................................................................................... 46

2.4.4.4 Символьные литералы..................................................................................................... 46

1.1.1.1 Строковые литералы................................................................................................... 47

2.4.4.5 Литерал null...................................................................................................................... 49

2.4.5 Операторы и знаки пунктуации............................................................................................. 49

2.5 Препроцессорные директивы....................................................................................................... 50

2.5.1 Символы условной компиляции............................................................................................ 51

2.5.2 Препроцессорные выражения................................................................................................ 51

2.5.3 Директивы объявлений........................................................................................................... 52

2.5.4 Директивы условной компиляции......................................................................................... 53

2.5.5 Директивы диагностики......................................................................................................... 55

2.5.6 Директивы областей............................................................................................................... 56

2.5.7 Директивы строк..................................................................................................................... 56

2.5.8 Директивы pragma.................................................................................................................. 57

2.5.8.1 Директива pragma warning............................................................................................... 57

3. Основные принципы....................................................................................................................... 59

3.1 Запуск приложений....................................................................................................................... 59

3.2 Завершение приложения............................................................................................................... 60

3.3 Объявления.................................................................................................................................... 60

3.4 Члены............................................................................................................................................. 62

3.4.1 Члены пространства имен...................................................................................................... 63

3.4.2 Члены структуры.................................................................................................................... 63

3.4.3 Члены перечисления.............................................................................................................. 63

3.4.4 Члены класса.......................................................................................................................... 63

3.4.5 Члены интерфейса.................................................................................................................. 64

3.4.6 Члены массива........................................................................................................................ 64

3.4.7 Члены делегата....................................................................................................................... 64

3.5 Доступ для членов........................................................................................................................ 64

3.5.1 Объявленная доступность...................................................................................................... 64

3.5.2 Домены доступности.............................................................................................................. 65

3.5.3 Защищенный доступ для членов экземпляров...................................................................... 68

3.5.4 Ограничения доступности..................................................................................................... 69

3.6 Сигнатуры и перегрузка............................................................................................................... 69

3.7 Области......................................................................................................................................... 71

3.7.1 Скрытие имени....................................................................................................................... 73

3.7.1.1 Скрытие через вложение.................................................................................................. 74

3.7.1.2 Скрытие через наследование........................................................................................... 74

3.8 Имена пространств имен и типов................................................................................................ 76

3.8.1 Полные имена......................................................................................................................... 78

3.9 Автоматическое управление памятью......................................................................................... 79

3.10 Порядок выполнения.................................................................................................................. 81

4. Типы................................................................................................................................................... 83

4.1 Типы значений............................................................................................................................... 83

4.1.1 Тип System.ValueType............................................................................................................ 84

4.1.2 Конструкторы по умолчанию................................................................................................ 84

4.1.3 Типы структуры...................................................................................................................... 85

4.1.4 Простые типы......................................................................................................................... 85

4.1.5 Целые типы............................................................................................................................. 86

4.1.6 Типы с плавающей запятой.................................................................................................... 88

4.1.7 Тип decimal............................................................................................................................. 89

4.1.8 Тип bool................................................................................................................................... 89

4.1.9 Перечисляемые типы............................................................................................................. 90

4.1.10 Обнуляемые типы................................................................................................................. 90

4.2 Ссылочные типы........................................................................................................................... 91

4.2.1 Типы классов.......................................................................................................................... 91

4.2.2 Тип объекта............................................................................................................................. 92

4.2.3 Строковый тип........................................................................................................................ 92

4.2.4 Типы интерфейса.................................................................................................................... 92

4.2.5 Типы массивов........................................................................................................................ 92

4.2.6 Типы делегатов....................................................................................................................... 92

4.3 Упаковка и распаковка.................................................................................................................. 93

4.3.1 Преобразования упаковки...................................................................................................... 93

4.3.2 Преобразования распаковки.................................................................................................. 94

4.4 Сформированные типы................................................................................................................. 95

4.4.1 Аргументы типа...................................................................................................................... 96

4.4.2 Открытые и закрытые типы................................................................................................... 96

4.4.3 Связанные и несвязанные типы.............................................................................................. 97

4.4.4 Соблюдение ограничений...................................................................................................... 97

4.5 Параметры типа............................................................................................................................ 98

4.6 Типы дерева выражений............................................................................................................... 99

5. Переменные..................................................................................................................................... 101

5.1 Категории переменных............................................................................................................... 101

5.1.1 Статические переменные..................................................................................................... 101

5.1.2 Переменные экземпляра....................................................................................................... 101

5.1.2.1 Переменные экземпляра в классах................................................................................ 102

5.1.2.2 Переменные экземпляра в структурах.......................................................................... 102

5.1.3 Элементы массива................................................................................................................ 102

5.1.4 Параметры по значению...................................................................................................... 102

5.1.5 Параметры по ссылке........................................................................................................... 102

5.1.6 Выходные параметры........................................................................................................... 103

5.1.7 Локальные переменные........................................................................................................ 103

5.2 Значения по умолчанию............................................................................................................. 104

5.3 Определенное присваивание...................................................................................................... 104

5.3.1 Переменные с начальным значением.................................................................................. 105

5.3.2 Переменные без начального значения................................................................................ 106

5.3.3 Точные правила для выявления определенного присваивания.......................................... 106

5.3.3.1 Общие правила для операторов.................................................................................... 106

5.3.3.2 Операторы блока, проверенные и непроверенные операторы.................................... 107

5.3.3.3 Операторы выражений................................................................................................... 107

5.3.3.4 Операторы объявления.................................................................................................. 107

5.3.3.5 Операторы «If».............................................................................................................. 107

5.3.3.6 Операторы «switch»....................................................................................................... 108

5.3.3.7 Операторы «while»......................................................................................................... 108

5.3.3.8 Операторы «do»............................................................................................................. 108

5.3.3.9 Операторы «for»............................................................................................................ 109

5.3.3.10 Операторы break, continue и goto................................................................................ 109

5.3.3.11 Операторы throw.......................................................................................................... 109

5.3.3.12 Операторы return.......................................................................................................... 109

5.3.3.13 Операторы «try-catch»................................................................................................. 110

5.3.3.14 Операторы «try-finally»............................................................................................... 110

5.3.3.15 Операторы «try-catch-finally»...................................................................................... 110

5.3.3.16 Операторы «foreach»................................................................................................... 111

5.3.3.17 Операторы «using»....................................................................................................... 111

5.3.3.18 Операторы «lock»........................................................................................................ 111

5.3.3.19 Операторы «yield»....................................................................................................... 112

5.3.3.20 Общие правила для простых выражений.................................................................... 112

5.3.3.21 Общие правила для выражений с внедренными выражениями................................. 112

5.3.3.22 Выражения вызова и выражения создания объекта.................................................... 112

5.3.3.23 Простые выражения присваивания.............................................................................. 113

5.3.3.24 Выражения &&............................................................................................................. 113

5.3.3.25 Выражения ||................................................................................................................. 114

5.3.3.26 Выражения «!».............................................................................................................. 115

5.3.3.27 ?? Выражения «??»....................................................................................................... 115

5.3.3.28 Выражения «?:»............................................................................................................ 116

5.3.3.29 Анонимные функции.................................................................................................... 116

5.4 Ссылочные переменные............................................................................................................. 117

5.5 Атомарность ссылок на переменные......................................................................................... 117

6. Преобразования.............................................................................................................................. 119

6.1 Неявные преобразования............................................................................................................ 119

6.1.1 Преобразование идентификатора........................................................................................ 119

6.1.2 Неявные преобразования числовых типов.......................................................................... 120

6.1.3 Неявные преобразования перечисляемых типов................................................................ 120

6.1.4 Неявные преобразования обнуляемых типов..................................................................... 120

6.1.5 Преобразования литерала null............................................................................................. 121

6.1.6 Неявные преобразования ссылочных типов....................................................................... 121

6.1.7 Преобразования упаковки.................................................................................................... 121

6.1.8 Неявные преобразования выражений констант.................................................................. 122

6.1.9 Неявные преобразования, включающие параметры типа.................................................. 122

6.1.10 Пользовательские неявные преобразования..................................................................... 122

6.1.11 Преобразования анонимных функций и преобразования группы методов..................... 123

6.2 Явные преобразования................................................................................................................ 123

6.2.1 Явные преобразования числовых типов.............................................................................. 123

6.2.2 Явные преобразования перечисляемых типов.................................................................... 125

6.2.3 Явные преобразования обнуляемых типов......................................................................... 125

6.2.4 Явные преобразования ссылочных типов........................................................................... 126

6.2.5 Преобразования распаковки................................................................................................ 126

6.2.6 Явные преобразования, включающие параметры типа...................................................... 127

6.2.7 Пользовательские явные преобразования........................................................................... 128

6.3 Стандартные преобразования.................................................................................................... 128

6.3.1 Стандартные неявные преобразования............................................................................... 128

6.3.2 Стандартные явные преобразования................................................................................... 128

6.4 Пользовательские преобразования............................................................................................ 128

6.4.1 Допустимые пользовательские преобразования................................................................. 128

6.4.2 Операторы преобразования с нулификацией...................................................................... 129

6.4.3 Вычисление пользовательских преобразований................................................................. 129

6.4.4 Пользовательские неявные преобразования....................................................................... 130

6.4.5 Пользовательские явные преобразования........................................................................... 131

6.5 Преобразования анонимных функций....................................................................................... 132

6.5.1 Вычисление преобразования анонимной функции к типу делегата.................................. 133

6.5.2 Вычисление преобразования анонимной функции к типу дерева выражений.................. 134

6.5.3 Пример реализации.............................................................................................................. 134

6.6 Преобразования группы методов............................................................................................... 137

7. Выражения...................................................................................................................................... 141

7.1 Классы выражений...................................................................................................................... 141

7.1.1 Значения выражений............................................................................................................ 142

7.2 Операторы................................................................................................................................... 142

7.2.1 Приоритет и ассоциативность операторов......................................................................... 143

7.2.2 Перегрузка операторов........................................................................................................ 144

7.2.3 Разрешение перегрузки унарных операторов..................................................................... 145

7.2.4 Разрешение перегрузки бинарных операторов................................................................... 145

7.2.5 Пользовательские операторы-кандидаты........................................................................... 146

7.2.6 Числовое расширение.......................................................................................................... 146

7.2.6.1 Числовое расширение унарных операторов................................................................. 146

7.2.6.2 Числовое расширение бинарных операторов............................................................... 146

7.2.7 Операторы с нулификацией................................................................................................. 147

7.3 Поиск членов............................................................................................................................... 148

7.3.1 Базовые типы........................................................................................................................ 149

7.4 Функции-члены........................................................................................................................... 150

7.4.1 Списки аргументов............................................................................................................... 153

7.4.2 Вывод типа............................................................................................................................ 155

7.4.2.1 Первый этап.................................................................................................................... 156

7.4.2.2 Второй этап.................................................................................................................... 156

7.4.2.3 Типы ввода...................................................................................................................... 156

7.4.2.4 Типы вывода................................................................................................................... 156

7.4.2.5 Зависимость.................................................................................................................... 157

7.4.2.6 Вывод типа вывода......................................................................................................... 157

7.4.2.7 Вывод явных типов параметров.................................................................................... 157

7.4.2.8 Точный вывод................................................................................................................. 157

7.4.2.9 Вывод нижних границ.................................................................................................... 157

7.4.2.10 Фиксирование............................................................................................................... 158

7.4.2.11 Выведенный тип возвращаемого значения................................................................. 158

7.4.2.12 Вывод типа при преобразовании групп методов........................................................ 159

7.4.2.13 Поиск наиболее подходящего общего типа для набора выражений......................... 159

7.4.3 Разрешение перегрузки........................................................................................................ 160

7.4.3.1 Применимая функция-член............................................................................................ 160

7.4.3.2 Более подходящая функция-член.................................................................................. 161

7.4.3.3 Лучшее преобразование из выражения......................................................................... 162

7.4.3.4 Лучшее преобразование из типа................................................................................... 162

7.4.3.5 Перегрузка в универсальных классах........................................................................... 163

7.4.4 Вызов функции-члена........................................................................................................... 163

7.4.4.1 Вызов в упакованных экземплярах................................................................................ 165

7.5 Основные выражения.................................................................................................................. 165

7.5.1 Литералы............................................................................................................................... 166

7.5.2 Простые имена..................................................................................................................... 166

7.5.2.1 Инвариантность значения в блоках............................................................................... 167

7.5.3 Выражения со скобками....................................................................................................... 168

7.5.4 Метод доступа к члену........................................................................................................ 168

7.5.4.1 Идентичные простые имена и имена типов.................................................................. 170

7.5.4.2 Грамматическая неоднозначность................................................................................ 171

7.5.5 Выражения вызова................................................................................................................ 171

7.5.5.1 Вызовы методов............................................................................................................. 172

7.5.5.2 Вызовы методов расширения........................................................................................ 173

7.5.5.3 Вызовы делегатов........................................................................................................... 175

7.5.6 Метод доступа к элементу................................................................................................... 176

7.5.6.1 Доступ к массиву........................................................................................................... 176

7.5.6.2 Доступ к индексатору.................................................................................................... 177

7.5.7 Доступ this............................................................................................................................ 177

7.5.8 Доступ base........................................................................................................................... 178

7.5.9 Постфиксные операторы инкремента и декремента.......................................................... 179

7.5.10 Оператор new..................................................................................................................... 180

7.5.10.1 Выражения создания объектов.................................................................................... 180

7.5.10.2 Инициализаторы объектов........................................................................................... 181

7.5.10.3 Инициализаторы коллекции........................................................................................ 183

7.5.10.4 Выражения создания массива...................................................................................... 184

7.5.10.5 Выражения создания делегата..................................................................................... 186

7.5.10.6 Выражения создания анонимных объектов................................................................ 188

7.5.11 Оператор typeof.................................................................................................................. 189

7.5.12 Операторы checked и unchecked....................................................................................... 191

7.5.13 Выражения значения по умолчанию................................................................................. 193

7.5.14 Выражения анонимного метода......................................................................................... 194

7.6 Унарные операторы.................................................................................................................... 194

7.6.1 Унарный оператор «плюс».................................................................................................. 194

7.6.2 Унарный оператор «минус»................................................................................................ 194

7.6.3 Оператор логического отрицания....................................................................................... 195

7.6.4 Оператор побитового дополнения...................................................................................... 195

7.6.5 Префиксные операторы инкремента и декремента............................................................ 195

7.6.6 Выражения приведения типа................................................................................................ 196

7.7 Арифметические операторы...................................................................................................... 197

7.7.1 Оператор произведения....................................................................................................... 197

7.7.2 Оператор деления................................................................................................................. 198

7.7.3 Оператор остатка................................................................................................................. 199

7.7.4 Оператор сложения.............................................................................................................. 200

7.7.5 Оператор вычитания............................................................................................................ 202

7.8 Операторы сдвига....................................................................................................................... 204

7.9 Операторы отношения и проверки типа.................................................................................... 205

7.9.1 Операторы сравнения целых чисел..................................................................................... 206

7.9.2 Операторы сравнения чисел с плавающей запятой............................................................ 206

7.9.3 Операторы сравнения десятичных чисел............................................................................ 207

7.9.4 Логические операторы равенства........................................................................................ 207

7.9.5 Операторы сравнения значений перечисления................................................................... 208

7.9.6 Операторы равенства значений ссылочного типа.............................................................. 208

7.9.7 Операторы равенства строк................................................................................................. 209

7.9.8 Операторы равенства делегатов.......................................................................................... 210

7.9.9 Операторы равенства и значение null................................................................................. 210

7.9.10 Оператор is......................................................................................................................... 210

7.9.11 Оператор as......................................................................................................................... 211

7.10 Логические операторы.............................................................................................................. 212

7.10.1 Логические операторы для целых чисел........................................................................... 212

7.10.2 Логические операторы для перечислений........................................................................ 213

7.10.3 Логические операторы....................................................................................................... 213

7.10.4 Обнуляемые логические операторы.................................................................................. 213

7.11 Условные логические операторы............................................................................................. 214

7.11.1 Логические условные операторы...................................................................................... 214

7.11.2 Пользовательские условные логические операторы........................................................ 214

7.12 Оператор слияния с null........................................................................................................... 215

7.13 Условный оператор.................................................................................................................. 216

7.14 Выражения анонимных функций.............................................................................................. 217

7.14.1 Подписи анонимных функций........................................................................................... 218

7.14.2 Тела анонимных функций.................................................................................................. 219

7.14.3 Разрешение перегрузки...................................................................................................... 219

7.14.4 Внешние переменные......................................................................................................... 220

7.14.4.1 Захваченные внешние переменные............................................................................. 220

7.14.4.2 Создание экземпляров локальных переменных.......................................................... 221

7.14.5 Вычисление выражений анонимных функций.................................................................. 223

7.15 Выражения запросов................................................................................................................. 223

7.15.1 Неоднозначность в выражениях запросов........................................................................ 224

7.15.2 Перевод выражений запросов............................................................................................ 225

7.15.2.1 Предложения select и groupby с продолжениями....................................................... 225

7.15.2.2 Явные типы переменных диапазона............................................................................ 226

7.15.2.3 Выражения вырожденных запросов............................................................................ 226

7.15.2.4 Предложения from, let, where, join и orderby.............................................................. 227

7.15.2.5 Предложения select...................................................................................................... 230

7.15.2.6 Предложения groupby.................................................................................................. 230

7.15.2.7 Прозрачные идентификаторы...................................................................................... 231

7.15.3 Шаблон выражения запроса............................................................................................... 232

7.16 Операторы присваивания......................................................................................................... 233

7.16.1 Простое присваивание....................................................................................................... 234

7.16.2 Сложное присваивание....................................................................................................... 236

7.16.3 Присваивание событий....................................................................................................... 237

7.17 Выражение................................................................................................................................. 237

7.18 Константные выражения........................................................................................................... 237

7.19 Логические выражения............................................................................................................. 239

8. Операторы языка........................................................................................................................... 240

8.1 Конечные точки и достижимость............................................................................................... 240

8.2 Блоки........................................................................................................................................... 242

8.2.1 Списки операторов............................................................................................................... 242

8.3 Пустой оператор......................................................................................................................... 243

8.4 Помеченные операторы.............................................................................................................. 243

8.5 Операторы объявления............................................................................................................... 244

8.5.1 Объявления локальных переменных................................................................................... 244

8.5.2 Объявления локальных констант......................................................................................... 246

8.6 Операторы-выражения................................................................................................................ 246

8.7 Операторы выбора...................................................................................................................... 246

8.7.1 Оператор if........................................................................................................................... 247

8.7.2 Оператор switch.................................................................................................................... 247

8.8 Операторы итераций................................................................................................................... 251

8.8.1 Оператор while..................................................................................................................... 251

8.8.2 Оператор do.......................................................................................................................... 252

8.8.3 Оператор for......................................................................................................................... 252

8.8.4 Оператор foreach.................................................................................................................. 253

8.9 Операторы перехода................................................................................................................... 256

8.9.1 Оператор break..................................................................................................................... 257

8.9.2 Оператор continue................................................................................................................ 258

8.9.3 Оператор goto....................................................................................................................... 258

8.9.4 Оператор return..................................................................................................................... 259

8.9.5 Оператор throw..................................................................................................................... 260

8.10 Оператор try.............................................................................................................................. 261

8.11 Операторы checked и unchecked.............................................................................................. 264

8.12 Оператор lock............................................................................................................................ 264

8.13 Оператор using.......................................................................................................................... 265

8.14 Оператор yield........................................................................................................................... 267

9. Пространства имен......................................................................................................................... 269

9.1 Единицы компиляции.................................................................................................................. 269

9.2 Объявления пространства имен................................................................................................. 269

9.3 Внешние псевдонимы................................................................................................................. 271

9.4 Директивы using.......................................................................................................................... 271

9.4.1 Директивы using alias............................................................................................................ 272

9.4.2 Директивы using namespace................................................................................................. 274

9.5 Члены пространства имен.......................................................................................................... 276

9.6 Объявления типов....................................................................................................................... 276

9.7 Квалификаторы псевдонима пространства имен...................................................................... 277

9.7.1 Уникальность псевдонимов................................................................................................. 278

10. Classes............................................................................................................................................. 281

10.1 Объявления классов.................................................................................................................. 281

10.1.1 Модификаторы классов...................................................................................................... 281

10.1.1.1 Абстрактные классы.................................................................................................... 282

10.1.1.2 Запечатанные классы................................................................................................... 282

10.1.1.3 Статические классы..................................................................................................... 283

10.1.2 Модификатор partial........................................................................................................... 284

10.1.3 Параметры типа.................................................................................................................. 284

10.1.4 Спецификация базы класса................................................................................................ 284

10.1.4.1 Базовые классы............................................................................................................. 284

10.1.4.2 Реализации интерфейсов.............................................................................................. 286

10.1.5 Ограничения параметров типа........................................................................................... 286

10.1.6 Тело класса......................................................................................................................... 290

10.2 Разделяемые типы..................................................................................................................... 290

10.2.1 Атрибуты............................................................................................................................ 291

10.2.2 Модификаторы................................................................................................................... 291

10.2.3 Параметры и ограничения типа......................................................................................... 292

10.2.4 Базовый класс..................................................................................................................... 292

10.2.5 Базовые интерфейсы.......................................................................................................... 292

10.2.6 Члены.................................................................................................................................. 293

10.2.7 Разделяемые методы.......................................................................................................... 293

10.2.8 Привязка имен.................................................................................................................... 296

10.3 Члены класса............................................................................................................................. 296

10.3.1 Тип экземпляра................................................................................................................... 298

10.3.2 Члены сформированных типов.......................................................................................... 298

10.3.3 Наследование...................................................................................................................... 299

10.3.4 Модификатор new.............................................................................................................. 300

10.3.5 Модификаторы доступа..................................................................................................... 300

10.3.6 Составные типы.................................................................................................................. 300

10.3.7 Статические члены и члены экземпляра........................................................................... 300

10.3.8 Вложенные типы................................................................................................................. 301

10.3.8.1 Полные имена............................................................................................................... 302

10.3.8.2 Объявленная доступность........................................................................................... 302

10.3.8.3 Скрытие........................................................................................................................ 302

10.3.8.4 Доступ this.................................................................................................................... 303

10.3.8.5 Доступ к частным и защищенным членам типа-контейнера..................................... 303

10.3.8.6 Вложенные типы в универсальных классах................................................................ 304

10.3.9 Зарезервированные имена членов..................................................................................... 305

10.3.9.1 Имена членов, зарезервированные для свойств......................................................... 305

10.3.9.2 Имена членов, зарезервированные для событий........................................................ 306

10.3.9.3 Имена членов, зарезервированные для индексаторов............................................... 306

10.3.9.4 Имена членов, зарезервированные для деструкторов............................................... 306

10.4 Константы................................................................................................................................. 307

10.5 Поля........................................................................................................................................... 308

10.5.1 Статические поля и поля экземпляров.............................................................................. 309

10.5.2 Поля только для чтения..................................................................................................... 310

10.5.2.1 Использование статических полей только для чтения вместо констант.................. 310

10.5.2.2 Отслеживание версий констант и статических полей только для чтения................. 311

10.5.3 Поля с модификатором volatile......................................................................................... 311

10.5.4 Инициализация поля........................................................................................................... 313

10.5.5 Инициализаторы переменных............................................................................................ 313

10.5.5.1 Инициализация статического поля.............................................................................. 314

10.5.5.2 Инициализация поля экземпляра................................................................................. 315

10.6 Методы...................................................................................................................................... 315

10.6.1 Параметры метода............................................................................................................. 317

10.6.1.1 Параметры по значению.............................................................................................. 318

10.6.1.2 Параметры по ссылке.................................................................................................. 319

10.6.1.3 Выходные параметры.................................................................................................. 320

10.6.1.4 Массивы параметров.................................................................................................... 320

10.6.2 Статические методы и методы экземпляра....................................................................... 323

10.6.3 Виртуальные методы......................................................................................................... 323

10.6.4 Переопределяющие методы.............................................................................................. 325

10.6.5 Запечатанные методы........................................................................................................ 327

10.6.6 Абстрактные методы.......................................................................................................... 328

10.6.7 Внешние методы................................................................................................................ 329

10.6.8 Разделяемые методы.......................................................................................................... 330

10.6.9 Методы расширения........................................................................................................... 330

10.6.10 Тело метода...................................................................................................................... 331

10.6.11 Перегрузка метода........................................................................................................... 331

10.7 Свойства.................................................................................................................................... 331

10.7.1 Статические свойства и свойства экземпляра................................................................... 333

10.7.2 Методы доступа................................................................................................................. 333

10.7.3 Автоматически реализуемые свойства............................................................................. 338

10.7.4 Доступность........................................................................................................................ 339

10.7.5 Виртуальные, запечатанные, переопределяющие и абстрактные методы доступа........ 340

10.8 События..................................................................................................................................... 342

10.8.1 События, подобные полям................................................................................................. 344

10.8.2 Методы доступа к событиям............................................................................................. 345

10.8.3 Статические события и события экземпляров.................................................................. 346

10.8.4 Виртуальные, запечатанные, переопределяющие и абстрактные методы доступа........ 347

10.9 Индексаторы............................................................................................................................. 347

10.9.1 Перегрузка индексатора..................................................................................................... 351

10.10 Операторы............................................................................................................................... 351

10.10.1 Унарные операторы......................................................................................................... 352

10.10.2 Бинарные операторы........................................................................................................ 353

10.10.3 Операторы преобразования............................................................................................. 353

10.11 Конструкторы экземпляров.................................................................................................... 356

10.11.1 Инициализаторы конструкторов..................................................................................... 357

10.11.2 Инициализаторы переменных экземпляров.................................................................... 358

10.11.3 Выполнение конструктора............................................................................................... 358

10.11.4 Конструкторы по умолчанию.......................................................................................... 360

10.11.5 Закрытые конструкторы................................................................................................... 360

10.11.6 Необязательные параметры конструктора экземпляров................................................ 361

10.12 Статические конструкторы.................................................................................................... 361

10.13 Деструкторы............................................................................................................................ 363

10.14 Итераторы............................................................................................................................... 365

10.14.1 Интерфейсы перечислителя............................................................................................. 365

10.14.2 Перечислимые интерфейсы............................................................................................. 365

10.14.3 Тип yield............................................................................................................................ 365

10.14.4 Объекты перечислителя................................................................................................... 365

10.14.4.1 Метод MoveNext........................................................................................................ 366

10.14.4.2 Свойство Current........................................................................................................ 367

10.14.4.3 Метод Dispose............................................................................................................ 367

10.14.5 Перечислимые объекты................................................................................................... 368

10.14.5.1 Метод GetEnumerator................................................................................................. 368

10.14.6 Пример реализации.......................................................................................................... 368

11. Структуры..................................................................................................................................... 375

11.1 Объявления структур................................................................................................................ 375

11.1.1 Модификаторы структуры................................................................................................. 375

11.1.2 Модификатор partial........................................................................................................... 376

11.1.3 Интерфейсы структуры...................................................................................................... 376

11.1.4 Тело структуры.................................................................................................................. 376

11.2 Члены структуры...................................................................................................................... 376

11.3 Различия между классом и структурой................................................................................... 376

11.3.1 Семантика значений........................................................................................................... 377

11.3.2 Наследование...................................................................................................................... 378

11.3.3 Присваивание...................................................................................................................... 378

11.3.4 Значения по умолчанию..................................................................................................... 378

11.3.5 Упаковка и распаковка....................................................................................................... 379

11.3.6 Действие ключевого слова this.......................................................................................... 381

11.3.7 Инициализаторы полей...................................................................................................... 381

11.3.8 Конструкторы..................................................................................................................... 381

11.3.9 Деструкторы....................................................................................................................... 382

11.3.10 Статические конструкторы.............................................................................................. 382

11.4 Примеры структур.................................................................................................................... 382

11.4.1 Тип целочисленного значения в базе данных................................................................... 382

11.4.2 Логический тип базы данных............................................................................................. 384

12. Массивы........................................................................................................................................ 387

12.1 Типы массива............................................................................................................................ 387

12.1.1 Тип System.Array................................................................................................................ 388

12.1.2 Массивы и универсальный интерфейс IList...................................................................... 388

12.2 Создание массива...................................................................................................................... 389

12.3 Доступ к элементам массива.................................................................................................... 389

12.4 Члены массива.......................................................................................................................... 389

12.5 Ковариация массивов................................................................................................................ 389

12.6 Инициализаторы массива......................................................................................................... 390

13. Интерфейсы................................................................................................................................... 393

13.1 Объявления интерфейсов......................................................................................................... 393

13.1.1 Модификаторы интерфейса............................................................................................... 393

13.1.2 Модификатор partial........................................................................................................... 393

13.1.3 Базовые интерфейсы.......................................................................................................... 394

13.1.4 Тело интерфейса................................................................................................................. 394

13.2 Члены интерфейса.................................................................................................................... 395

13.2.1 Методы интерфейса........................................................................................................... 396

13.2.2 Свойства интерфейса......................................................................................................... 396

13.2.3 События интерфейса.......................................................................................................... 396

13.2.4 Индексаторы интерфейса................................................................................................... 396

13.2.5 Доступ к членам интерфейса............................................................................................. 397

13.3 Полные имена членов интерфейса........................................................................................... 399

13.4 Реализации интерфейсов.......................................................................................................... 399

13.4.1 Явные реализации членов интерфейса.............................................................................. 400

13.4.2 Уникальность реализованных интерфейсов..................................................................... 402

13.4.3 Реализация универсальных методов................................................................................. 403

13.4.4 Сопоставление интерфейсов.............................................................................................. 404

13.4.5 Наследование реализаций интерфейсов............................................................................ 407

13.4.6 Повторная реализация интерфейса................................................................................... 408

13.4.7 Абстрактные классы и интерфейсы................................................................................... 409

14. Перечисляемые типы................................................................................................................... 411

14.1 Объявления перечислений........................................................................................................ 411

14.2 Модификаторы перечисления.................................................................................................. 412

14.3 Члены перечисления................................................................................................................. 412

14.4 Тип System.Enum....................................................................................................................... 414

14.5 Значения перечисления и операции......................................................................................... 414

15. Делегаты........................................................................................................................................ 415

15.1 Объявления делегатов.............................................................................................................. 415

15.2 Совместимость делегатов........................................................................................................ 417

15.3 Создание экземпляра делегата................................................................................................. 417

15.4 Вызов делегата.......................................................................................................................... 418

16. Исключения.................................................................................................................................. 421

16.1 Причины исключений............................................................................................................... 421

16.2 Класс System.Exception............................................................................................................. 421

16.3 Обработка исключений............................................................................................................. 421

16.4 Общие классы исключений...................................................................................................... 422

17. Атрибуты....................................................................................................................................... 425

17.1 Классы атрибутов..................................................................................................................... 425

17.1.1 Использование атрибутов.................................................................................................. 425

17.1.2 Позиционные и именованные параметры......................................................................... 426

17.1.3 Типы параметров атрибута................................................................................................ 427

17.2 Спецификация атрибута........................................................................................................... 427

17.3 Экземпляры атрибутов............................................................................................................. 433

17.3.1 Компиляция атрибута........................................................................................................ 433

17.3.2 Извлечение экземпляра атрибута во время выполнения.................................................. 433

17.4 Зарезервированные атрибуты.................................................................................................. 433

17.4.1 Атрибут AttributeUsage...................................................................................................... 434

17.4.2 Атрибут Conditional........................................................................................................... 434

17.4.2.1 Условные методы......................................................................................................... 435

17.4.2.2 Классы условных атрибутов....................................................................................... 437

17.4.3 Атрибут Obsolete................................................................................................................ 437

17.5 Атрибуты для взаимодействия................................................................................................. 438

17.5.1 Взаимодействие с компонентами COM и Win32.............................................................. 438

17.5.1.1 Взаимодействие с другими языками .NETАтрибут IndexerName............................. 439

18. Небезопасный код........................................................................................................................ 441

18.1 Небезопасные контексты.......................................................................................................... 441

18.2 Типы указателя.......................................................................................................................... 443

18.3 Фиксированные и перемещаемые переменные....................................................................... 446

18.4 Преобразования указателей..................................................................................................... 446

18.5 Указатели в выражениях........................................................................................................... 447

18.5.1 Косвенное обращение по указателю................................................................................. 448

18.5.2 Доступ к члену по указателю............................................................................................ 448

18.5.3 Доступ к элементу по указателю....................................................................................... 449

18.5.4 Оператор адреса................................................................................................................. 450

18.5.5 Увеличение и уменьшение указателя................................................................................ 451

18.5.6 Арифметические операции с указателем.......................................................................... 451

18.5.7 Сравнение указателей........................................................................................................ 452

18.5.8 Оператор sizeof.................................................................................................................. 452

18.6 Оператор fixed.......................................................................................................................... 453

18.7 Буферы фиксированного размера............................................................................................ 457

18.7.1 Объявления буферов фиксированного размера................................................................ 457

18.7.2 Буферы фиксированного размера в выражениях.............................................................. 458

18.7.3 Проверка определенного присваивания............................................................................ 459

18.8 Выделение стека....................................................................................................................... 459

18.9 Динамическое выделение памяти............................................................................................ 460

A. Комментарии к документации.................................................................................................... 463

A.1 Введение..................................................................................................................................... 463

A.2 Рекомендуемые теги.................................................................................................................. 464

A.2.1 Тег <c>.................................................................................................................................. 465

A.2.2 Тег <code>............................................................................................................................ 465

A.2.3 Тег <example>...................................................................................................................... 466

A.2.4 Тег <exception>.................................................................................................................... 466

A.2.5 Тег <include>........................................................................................................................ 467

A.2.6 Тег <list>............................................................................................................................... 467

A.2.7 Тег <para>............................................................................................................................. 468

A.2.8 Тег <param>.......................................................................................................................... 469

A.2.9 Тег <paramref>..................................................................................................................... 469

A.2.10 Тег <permission>................................................................................................................. 469

A.2.11 Тег <summary>................................................................................................................... 470

A.2.12 Тег <returns>....................................................................................................................... 470

A.2.13 Тег <see>............................................................................................................................. 471

A.2.14 Тег <seealso>...................................................................................................................... 471

A.2.15 Тег <summary>................................................................................................................... 471

A.2.16 Тег <value>......................................................................................................................... 472

A.2.17 Тег <typeparam>................................................................................................................. 472

A.2.18 Тег <typeparamref>............................................................................................................. 472

A.3 Обработка файла документации............................................................................................... 473

A.3.1 Формат строки идентификатора......................................................................................... 473

A.3.2 Примеры строк идентификаторов...................................................................................... 474

A.4 Пример........................................................................................................................................ 477

A.4.1 Исходный код C#................................................................................................................. 477

A.4.2 Результирующий XML........................................................................................................ 480

B. Грамматика.................................................................................................................................... 483

B.1 Лексика........................................................................................................................................ 483

B.1.1 Знаки завершения строки.................................................................................................... 483

B.1.2 Комментарии........................................................................................................................ 483

B.1.3 Пробел.................................................................................................................................. 484

B.1.4 Маркеры............................................................................................................................... 484

B.1.5 Управляющие последовательности символов Юникода................................................... 484

B.1.6 Идентификаторы.................................................................................................................. 484

B.1.7 Ключевые слова................................................................................................................... 486

B.1.8 Литералы.............................................................................................................................. 486

B.1.9 Операторы и знаки пунктуации........................................................................................... 488

B.1.10 Директивы предварительной обработки........................................................................... 488

B.2 Синтаксис.................................................................................................................................... 491

B.2.1 Базовые концепции.............................................................................................................. 491

B.2.2 Типы...................................................................................................................................... 491

B.2.3 Переменные.......................................................................................................................... 492

B.2.4 Выражения............................................................................................................................ 492

B.2.5 Операторы............................................................................................................................ 499

B.2.6 Пространства имен.............................................................................................................. 502

B.2.7 Классы.................................................................................................................................. 503

B.2.8 Структуры............................................................................................................................ 510

B.2.9 Массивы................................................................................................................................ 511

B.2.10 Интерфейсы........................................................................................................................ 511

B.2.11 Перечисления..................................................................................................................... 512

B.2.12 Делегаты............................................................................................................................. 513

B.2.13 Атрибуты............................................................................................................................ 513

B.3 Грамматические расширения для небезопасного кода............................................................ 514

C. Ссылки............................................................................................................................................ 519

 


1. Введение

C# (произносится как «Си-шарп» или «Си-диез») — это простой, современный, строго типизированный объектно-ориентированный язык программирования. C# базируется на семействе языков программирования C и будет хорошо знаком программистам, работавшим с языками C, C++ и Java. Язык C# стандартизирован ECMA на соответствие стандарту ECMA-334, а также ISO/IEC на соответствие стандарту ISO/IEC 23270. Компилятор Microsoft C# для платформы .NET Framework реализован в полном соответствии с требованиями обоих стандартов.

C# представляет собой объектно-ориентированный язык программирования, однако также обеспечивает поддержку компонентно_ориентированного программирования. Разработка современных приложений все в большей степени базируется на применении программных компонентов в форме автономных и самодокументируемых функциональных модулей. Основной особенностью таких компонентов является реализация модели программирования с использованием свойств, методов, событий и атрибутов, представляющих декларативное описание компонентов, а также включение в них собственной документации. В C# представлены языковые конструкции, непосредственно поддерживающие эти понятия, что делает его близким к естественному языком для создания и применения программных компонентов.

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

В C# применяется унифицированная система типов. Все типы C#, включая простые типы (например, int и double), наследуются от единственного корневого типа object. Таким образом, все типы используют набор общих операций, что обеспечивает согласованные хранение, передачу и обработку значений любого типа. Кроме того, в C# поддерживаются пользовательские ссылочные типы и типы значений, что обеспечивает динамическое размещение объектов в памяти и встроенное хранение упрощенных структур.

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

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

1.1 Программа «Hello world»

Программа «Hello, World» традиционно используется для знакомства с языком программирования. Ниже приведен пример этой программы на языке C#:

using System;

class Hello
{
static void Main() {
Console.WriteLine("Привет!");
}
}

Исходные файлы C# обычно имеют расширение cs. Если программа «Hello, World» сохранена в файле hello.cs, то чтобы скомпилировать ее с помощью компилятора Microsoft C#, введите следующую команду в командной строке:

csc hello.cs

В результате компиляции будет создан исполняемый файл сборки hello.exe. В результате выполнения этого приложения получаются следующие выходные данные:

Привет!

Программа «Hello, World» начинается с директивы using, которая ссылается на пространство имен System. Пространства имен используются для иерархического упорядочения программ и библиотек C#. Пространства имен могут содержать типы и другие пространства имен. Например, пространство имен System содержит набор типов (например, используемый в программе класс Console), а также ряд других пространств имен (например, IO и Collections). Директива using ссылается на заданное пространство имен и обеспечивает безусловное использование типов, являющихся его членами. Благодаря применению директивы using в программе можно использовать сокращенную форму записи Console.WriteLine вместо System.Console.WriteLine.

Класс Hello, объявленный в программе «Hello, World», содержит единственный член — метод Main. Метод Main объявляется с помощью модификатора static. В C# методы могут ссылаться на конкретный экземпляр вмещающего объекта с помощью зарезервированного слова this, однако статические методы могут функционировать, не ссылаясь на конкретный объект. По соглашению статический метод Main является точкой входа в программу.

Выходные данные программы формируются с помощью метода WriteLine класса Console, принадлежащего пространству имен System. Этот класс содержится в библиотеках классов платформы .NET Framework, на которые по умолчанию автоматически ссылается компилятор Microsoft C#. Обратите внимание, что в C# не используется собственная библиотека времени выполнения. Вместо этого в C# в качестве библиотеки времени выполнения используется платформа .NET Framework.

1.2 Структура программы

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

Пример:

using System;

namespace Acme.Collections
{
public class Stack
{
Entry top;

public void Push(object data) {
top = new Entry(top, data);
}

public object Pop() {
if (top == null) throw new InvalidOperationException();
object result = top.data;
top = top.next;
return result;
}

class Entry
{
public Entry next;
public object data;

public Entry(Entry next, object data) {
this.next = next;
this.data = data;
}
}
}
}

В примере объявляется класс Stack, принадлежащий пространству имен Acme.Collections. Полное имя этого класса — Acme.Collections.Stack. Этот класс содержит несколько членов: поле top, два метода Push и Pop, а также вложенный класс Entry. Класс Entry в свою очередь содержит три члена: поля next и data, а также конструктор. Если исходный код примера хранится в файле acme.cs, команда

csc /t:library acme.cs

компилирует пример в виде библиотеки (код без точки входа Main) и создает сборку с именем acme.dll.

Сборки содержат исполняемый код в форме инструкций промежуточного языка (Intermediate Language, IL), а также символьные данные в форме метаданных. Перед выполнением код IL сборки автоматически преобразуется в код для конкретного процессора с помощью JIT-компилятора среды .NET CLR.

Поскольку сборка представляет собой самодокументируемый функциональный модуль, содержащий как код, так и метаданные, в файлах заголовка C# не требуется использовать директивы #include. В программе C# возможность обращения к открытым типам и членам, содержащимся в конкретной сборке, реализуется посредством ссылки на эту сборку во время компиляции программы. Например, если в программе используется класс Acme.Collections.Stack, содержащийся в сборке acme.dll:

using System;
using Acme.Collections;

class Test
{
static void Main() {
Stack s = new Stack();
s.Push(1);
s.Push(10);
s.Push(100);
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
}
}

Если программа хранится в файле test.cs, во время его компиляции ссылка на сборку acme.dll реализуется с помощью параметра компилятора /r:

csc /r:acme.dll test.cs

В результате этого создается исполняемый файл сборки test.exe, при выполнении которого получаются следующие выходные данные:

100
10
1

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

1.3 Типы и переменные

В C# все типы подразделяются на две основные категории: типы значений и ссылочные типы. Переменные типа значений непосредственно содержат данные, тогда как переменные ссылочного типа хранят ссылки на соответствующие данные (объекты). Две переменные ссылочного типа могут ссылаться на один объект. Это позволяет изменять объект, на который ссылается одна переменная, выполняя соответствующие операции с другой. Каждая переменная типа значений содержит собственную копию данных. В связи с этим операции с одной переменной не влияют на другую (за исключением переменных параметров ref и out).

Типы значений в C# подразделяются на простые типы, перечисляемые типы, типы структуры и обнуляемые типы. Ссылочные типы в C# подразделяются на типы класса, типы интерфейса, типы массива и типы делегата.

В следующей таблице представлен обзор системы типов C#.

 

Категория Описание
Типы значений Простые типы Целые со знаком: sbyte, short, int, long
Целые без знака: byte, ushort, uint, ulong
Символы Юникода: char
IEEE с плавающей запятой: float, double
Десятичный с повышенной точностью: decimal
Логический: bool
Перечисляемые типы Пользовательские типы вида enum E {...}
Типы структуры Пользовательские типы вида struct S {...}
Обнуляемые типы Расширения любых других типов значений, включающие значение null
Ссылочные типы Типы классов Первичный базовый класс для всех типов: object
Строки Юникода: string
Пользовательские типы вида class C {...}
Типы интерфейса Пользовательские типы вида interface I {...}
Типы массивов Одно- и многомерные, например int[] и int[,]
Типы делегатов Пользовательские типы, например вида delegate int D(...)

 

Восемь целых типов обеспечивают поддержку 8-, 16-, 32- и 64-разрядных значений со знаком и без знака.

Два типа с плавающей запятой (float и double) представляются в 32-разрядном (одинарная точность) или 64-разрядном (двойная точность) формате IEEE 754.

Тип decimal представляет собой 128-разрядный тип данных, ориентированный на применение в финансовых и денежных вычислениях.

Тип bool в языке C# используется для представления логических значений — true или false.

Обработка знаков и строк в C# выполняется с применением кодировки Юникод. Тип char представляет элемент кода UTF-16, а тип string — последовательность элементов кода UTF-16.

В следующей таблице представлено краткое описание числовых типов C#.

 

Категория Разрядность Тип Диапазон и точность
Целые со знаком sbyte –128...127
short –32 768...32 767
int –2 147 483 648...2 147 483 647
long –9 223 372 036 854 775 808...9 223 372 036 854 775 807
Целые без знака byte 0...255
ushort 0...65 535
uint 0...4 294 967 295
ulong 0...18 446 744 073 709 551 615
С плавающей запятой float От 1,5 Ч 10−45 до 3,4 Ч 1038 с точностью до 7 знаков
double От 5,0 Ч 10−324 до 1,7 Ч 10308 с точностью до 15 знаков
Десятичный decimal От 1,0 Ч 10−28 до 7,9 Ч 1028 с точностью до 28 знаков

 

Для создания новых типов в программах C# используются объявления типов. В объявлении типа указываются имя и члены нового типа. В C# поддерживается определение пользователем типов пяти категорий: типы класса, типы структуры, типы интерфейса, перечисляемые типы и типы делегата.

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

Тип структуры, как и тип класса, представляет структуру, содержащую данные-члены и функции-члены. Однако структуры, в отличие от классов, являются типами значений и для них не требуется выделение памяти в куче. Типы структуры не поддерживают определяемое пользователем наследование. Все типы структуры неявно наследуются от типа object.

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

Тип делегата представляет ссылки на методы с конкретным списком параметров и типом возвращаемого значения. С помощью делегатов методы обрабатываются как сущности, которым можно передавать параметры и которые можно присваивать переменным. Понятие делегата близко к понятию указателя на функцию, используемому в некоторых других языках. Однако делегаты, в отличие от указателей на функции, представляют собой пример объектно-ориентированного и строго типизированного подхода к программированию.

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

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

В C# поддерживаются одно- и многомерные массивы любого типа. В отличие от типов, перечисленных выше, типы массива необязательно объявлять перед использованием. Чтобы сформировать тип массива, необходимо ввести квадратные скобки после имени массива. Например, int[] — это одномерный массив типа int; int[,] — двумерный массив типа int, а int[][] — одномерный массив, состоящий из одномерных массивов типа int.

Обнуляемые типы также не требуется объявлять перед использованием. Для каждого необнуляемого типа значений T существует соответствующий обнуляемый тип T?, который дополнительно содержит значение null. Например, тип int? может содержать любое 32-разрядное целое значение, а также значение null.

В C# применяется унифицированная система типов, в которой значение любого типа может обрабатываться как объект. В C# каждый тип прямо или косвенно наследуется от типа класса object, который является первичным базовым классом для всех типов. Обработка значений ссылочного типа как объектов выполняется посредством рассмотрения их как значений типа object. Значения типов значений обрабатываются как объекты с использованием операций упаковки и распаковки. В следующем примере значение типа int преобразуется к типу object и затем обратно к типу int.

using System;

class Test
{
static void Main() {
int i = 123;
object o = i; // Упаковка
int j = (int)o; // Распаковка
}
}

При преобразовании значения типа значений к типу object выделяется экземпляр объекта (также называется «ящик»), который будет содержать значение, после чего значение копируется в этот экземпляр. При обратном приведении ссылки на object к типу значений проверяется, является ли объект, на который существует ссылка, «ящиком» допустимого типа значений. В случае успешного прохождения проверки копируется значение из «ящика».

В унифицированной системе типов C# типы значений могут преобразовываться в объекты «по требованию». Благодаря унификации как ссылочные типы, так и типы значений могут использовать универсальные библиотеки, использующие тип object.

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

 

Тип переменной Возможное содержимое
Необнуляемый тип значений Значение такого же типа
Обнуляемый тип значений Значение null или значение такого же типа
object Пустая ссылка, ссылка на объект любого ссылочного типа или ссылка на упакованное значение любого типа значений
Тип класса Пустая ссылка, ссылка на экземпляр заданного типа класса или ссылка на экземпляр класса, производного от заданного типа
Тип интерфейса Пустая ссылка, ссылка на экземпляр типа класса, который реализует заданный тип интерфейса, или ссылка на упакованное значение типа значений, который реализует заданный тип интерфейса
Тип массива Пустая ссылка, ссылка на экземпляр заданного типа массива или ссылка на экземпляр совместимого типа массива
Тип делегата Пустая ссылка или ссылка на экземпляр заданного типа делегата

 

1.4 Выражения

Выражения состоят из операндов и операторов. Операторы выражения определяют операцию, которая выполняется с его операндами. Примеры операторов: «+», «-», «*», «/» и new. Примеры операндов: литералы, поля, локальные переменные и выражения.

Если выражение содержит несколько операторов, приоритет операторов управляет порядком, в котором происходит вычисление отдельных операторов. Например, выражение x + y * z вычисляется как x + (y * z), поскольку оператор «*» имеет более высокий приоритет по сравнению с оператором «+».

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

В следующей таблице приводится описание операторов C# в порядке убывания приоритета соответствующих категорий операторов. Операторы одной категории обладают одинаковым приоритетом.

 

Категория Выражение Описание
Основные x.m Доступ к членам
x(...) Вызов методов и делегатов
x[...] Доступ к массиву и индексатору
x++ Постфиксное приращение
x-- Постфиксное уменьшение
new T(...) Создание объекта и делегата
new T(...){...} Создание объекта с использованием инициализатора
new {...} Инициализатор анонимного объекта
new T[...] Создание массива
typeof(T) Получение объекта System.Type для T
checked(x) Вычисление выражения в контексте checked
unchecked(x) Вычисление выражения в контексте unchecked
default(T) Получение значения по умолчанию типа T
delegate {...} Анонимная функция (анонимный метод)
Унарные +x Идентификация
-x Отрицание
!x Логическое отрицание
~x Побитовое отрицание
++x Префиксное приращение
--x Префиксное уменьшение
(T)x Явное преобразование x к типу T
Мультипликативные x * y Умножение
x / y Деление
x % y Остаток
Аддитивные x + y Сложение, объединение строк, объединение делегатов
x – y Вычитание, удаление делегата

 

Операторы сдвига x << y Поразрядный сдвиг влево
x >> y Поразрядный сдвиг вправо
Операторы проверки отношения и типа x < y Меньше
x > y Больше
x <= y Меньше или равно
x >= y Больше или равно
x is T Возвращает true, если x имеет тип T; в противном случае возвращает false
x as T Возвращает значение x, типизированное как T, или null, если x имеет тип, отличный от T
Равенство x == y Равно
x != y Не равно
Логическое AND x & y Целое побитовое AND, логическое AND
Исключающее XOR x ^ y Целое побитовое исключающее XOR, логическое исключающее XOR
Логическое OR x | y Целое побитовое OR, логическое OR
Условное AND   x && y y вычисляется только в том случае, если x имеет значение true
Условное OR x || y y вычисляется только в том случае, если x имеет значение false
Слияние с null X ?? y Если x имеет значение null, вычисляется y; в противном случае вычисляется x
Условные x ? y : z Если x имеет значение true, вычисляется y; если x имеет значение false, вычисляется z
Присваивание или анонимная функция x = y Присваивание
x оператор= y Составное присваивание; поддерживаются следующие операторы: *= /= %= += -= <<= >>= &= ^= |=
(T x) => y Анонимная функция (лямбда-выражение)

 

1.5 Операторы

Действия программы выражаются с помощью операторов. В C# поддерживается несколько различных типов операторов, некоторые из которых определяются как внедренные операторы.

Блок позволяет записывать несколько операторов в контекстах, в которых допускается только один оператор. Блок состоит из нескольких операторов, записанных между разделителями — { и }.

Операторы объявления используются для объявления локальных переменных и констант.

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

Операторы выбора используются для выполнения одного из нескольких возможных операторов на основании значения какого-либо выражения. В этой группе представлены операторы if и switch.

Операторы итераций используются для повторяющегося выполнения внедренного оператора. В этой группе представлены операторы while, do, for и foreach.

Операторы перехода используются для передачи управления. В этой группе представлены операторы break, continue, goto, throw, return и yield.

Оператор try...catch используется для перехвата исключений, происходящих во время выполнения блока. Оператор try...finally используется для задания кода финализации, который выполняется всегда (независимо от того, возникло или нет исключение).

Операторы checked и unchecked используются для управления контекстом проверки переполнения при выполнении целочисленных математических операций и преобразований.

Оператор lock используется для получения блокировки взаимного исключения для заданного объекта, выполнения оператора и последующего снятия блокировки.

Оператор using используется для получения ресурса, выполнения оператора и последующего удаления ресурса.

В следующей таблице представлены операторы C# и примеры их использования.

 

Оператор Пример
Объявление локальной переменной static void Main() { int a; int b = 2, c = 3; a = 1; Console.WriteLine(a + b + c); }
Объявление локальной константы static void Main() { const float pi = 3.1415927f; const int r = 25; Console.WriteLine(pi * r * r); }
Оператор выражения static void Main() { int i; i = 123; // Оператор выражения Console.WriteLine(i); // Оператор выражения i++; // Оператор выражения Console.WriteLine(i); // Оператор выражения }
Оператор if static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Нет аргументов"); } else { Console.WriteLine("Один или более аргументов"); } }

 

Оператор switch static void Main(string[] args) { int n = args.Length; switch (n) { case 0: Console.WriteLine("Нет аргументов"); break; case 1: Console.WriteLine("Один аргумент"); break; default: Console.WriteLine("Аргументов: {0}", n); break; } } }
Оператор while static void Main(string[] args) { int i = 0; while (i < args.Length) { Console.WriteLine(args[i]); i++; } }
Оператор do static void Main() { string s; do { s = Console.ReadLine(); if (s != null) Console.WriteLine(s); } while (s != null); }
Оператор for static void Main(string[] args) { for (int i = 0; i < args.Length; i++) { Console.WriteLine(args[i]); } }
Оператор foreach static void Main(string[] args) { foreach (string s in args) { Console.WriteLine(s); } }
Оператор break static void Main() { while (true) { string s = Console.ReadLine(); if (s == null) break; Console.WriteLine(s); } }
Оператор continue static void Main(string[] args) { for (int i = 0; i < args.Length; i++) { if (args[i].StartsWith("/")) continue; Console.WriteLine(args[i]); } }

 

Оператор goto static void Main(string[] args) { int i = 0; goto check; loop: Console.WriteLine(args[i++]); check: if (i < args.Length) goto loop; }
Оператор return static int Add(int a, int b) { return a + b; } static void Main() { Console.WriteLine(Add(1, 2)); return; }
Оператор yield static IEnumerable<int> Range(int from, int to) { for (int i = from; i < to; i++) { yield return i; } yield break; } static void Main() { foreach (int x in Range(-10,10)) { Console.WriteLine(x); } }
Операторы throw и try static double Divide(double x, double y) { if (y == 0) throw new DivideByZeroException(); return x / y; } static void Main(string[] args) { try { if (args.Length != 2) { throw new Exception("Введите два числа"); } double x = double.Parse(args[0]); double y = double.Parse(args[1]); Console.WriteLine(Divide(x, y)); } catch (Exception e) { Console.WriteLine(e.Message); } finally { Console.WriteLine("Окончание работы"); } }
Операторы checked и unchecked static void Main() { int i = int.MaxValue; checked { Console.WriteLine(i + 1); // Исключение } unchecked { Console.WriteLine(i + 1); // Переполнение } }

 

Оператор lock class Account { decimal balance; public void Withdraw(decimal amount) { lock (this) { if (amount > balance) { throw new Exception("Недостаточно средств"); } balance -= amount; } } }
Оператор using static void Main() { using (TextWriter w = File.CreateText("test.txt")) { w.WriteLine("Строка 1"); w.WriteLine("Строка 2"); w.WriteLine("Строка 3"); } }

 

1.6 Классы и объекты

В C# классы представляют собой фундаментальные типы. Класс — это структура данных, объединяющая состояние (поля) и действия (методы и другие функции-члены). Класс предоставляет определения для динамически создаваемых экземпляров класса (также называются объектами). Классы поддерживают механизмы наследования и полиморфизма, которые позволяют создавать производные классы, расширяющие функциональные возможности базового класса.

Новые классы создаются с помощью объявлений класса. Объявление класса начинается с заголовка, в котором задаются атрибуты и модификаторы класса, имя класса, базовый класс (если есть), а также интерфейсы, реализуемые классом. За заголовком следует тело класса, которое содержит перечень объявлений членов, записанных между разделителями — { и }.

Ниже приведено объявление простого класса Point:

public class Point
{
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

Экземпляры класса создаются с помощью оператора new, который выделяет память для нового экземпляра, вызывает конструктор для инициализации экземпляра и возвращает ссылку на экземпляр. С помощью следующих операторов создаются два объекта Point, ссылки на которые сохраняются в двух переменных:

Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);

Память, занимаемая объектом, автоматически освобождается, если объект более не используется. В C# не обязательно (и не допускается) явно освобождать память от объектов.

1.6.1 Члены

Класс может содержать статические члены или члены экземпляра. Статические члены принадлежат классам. Члены экземпляра принадлежат объектам (экземплярам класса).

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

 

Член Описание
Константы Постоянные значения, связанные с классом
Поля Переменные класса
Методы Вычисления и действия, которые могут выполняться классом
Свойства Действия, связанные с чтением и записью именованных свойств класса
Индексаторы Действия, связанные с индексацией экземпляров класса, например массива
События Уведомления, которые могут формироваться классом
Операторы Операторы преобразования и выражений, поддерживаемые классом
Конструкторы Действия, необходимые для инициализации экземпляров класса или самого класса
Деструкторы Действия, выполняемые перед окончательным удалением экземпляров класса
Типы Вложенные типы, объявленные в классе

 

1.6.2 Доступность

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

 

Доступность Значение
public Доступ не ограничен
protected Доступ ограничен этим классом и унаследованными от него классами
internal Доступ ограничен этой программой
protected internal Доступ ограничен этой программой и классами, унаследованными от этого класса
private Доступ ограничен этим классом

 

1.6.3 Параметры типа

В определении класса может задаваться набор параметров типа. Список имен параметров типа указывается за именем класса в угловых скобках. Параметры класса могут использоваться в теле объявления класса для определения членов класса. В следующем примере для класса Pair задаются параметры типа TFirst и TSecond:

public class Pair<TFirst,TSecond>
{
public TFirst First;

public TSecond Second;
}

Тип класса, в объявлении которого принимаются параметры типа, называется универсальным типом класса. Типы структуры, интерфейса и делегата также могут быть универсальными.

При использовании универсального класса для каждого параметра типа необходимо указать аргументы типа:

Pair<int,string> pair = new Pair<int,string> { First = 1, Second = "два" };
int i = pair.First; // TFirst is int
string s = pair.Second; // TSecond is string

Универсальный тип с предоставленными аргументами типа, например Pair<int,string>, называется сформированным типом.

1.6.4 Базовые классы

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

public class Point
{
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

public class Point3D: Point
{
public int z;

public Point3D(int x, int y, int z): base(x, y) {
this.z = z;
}
}

Класс наследует члены своего базового класса. Наследование означает, что класс неявно содержит все члены своего базового класса (за исключением конструкторов базового класса). В производном классе можно добавлять новые члены, однако нельзя удалять определения унаследованных членов. В предыдущем примере класс Point3D наследует поля x и y класса Point. Это означает, что каждый экземпляр класса Point3D содержит три поля: x, y и z.

Существует неявное преобразование из типа класса к любому из его базовых типов класса. Таким образом, переменная типа класса может ссылаться на экземпляр этого класса или любого производного от него класса. Например, при использовании объявления предыдущего класса переменная типа Point может ссылаться как на экземпляр класса Point, так и на экземпляр класса Point3D:

Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);

1.6.5 Поля

Поле представляет собой переменную, связанную с классом или экземпляром класса.

Поле, объявленное с использованием модификатора static, определяет статическое поле. Статическое поле определяет только одно место хранения. Вне зависимости от количества создаваемых экземпляров класса всегда существует только одна копия статического поля.

Поле, объявленное без использования модификатора static, определяет поле экземпляра. Каждый экземпляр класса содержит отдельную копию всех полей экземпляра класса.

В следующем примере каждый экземпляр класса Color содержит отдельную копию полей экземпляра r, g и b. Однако существует только одна копия статических полей Black, White, Red, Green и Blue:

public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);

private byte r, g, b;

public Color(byte r, byte g, byte b) {
this.r = r;
this.g = g;
this.b = b;
}
}

В предыдущем примере показано объявление полей только для чтения с помощью модификатора readonly. Присваивание значения полю с модификатором readonly может выполняться только при объявлении поля или в конструкторе этого класса.

1.6.6 Методы

Метод — это член, реализующий вычисление или действие, которые могут выполняться объектом или классом. Доступ к статическим методам осуществляется через классы. Доступ к методам экземпляра осуществляется через экземпляры класса.

Метод содержит (возможно, пустой) список параметров, которые представляют собой значения или ссылки на переменные, передаваемые в метод, а также тип возвращаемого значения, который задает тип значения, вычисляемого и возвращаемого методом. Если метод не возвращает значение, ему присваивается тип возвращаемого значения void.

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

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

1.6.6.1 Параметры

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

Параметр значения используется для передачи входного параметра. Параметр значения соответствует локальной переменной, начальное значение которой получается из аргумента, передаваемого для этого параметра. Изменение параметра значения не влияет на аргумент, передаваемый для этого параметра.

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

using System;

class Test
{
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}

static void Main() {
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine("{0} {1}", i, j); // Результат: "2 1"
}
}

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

using System;

class Test
{
static void Divide(int x, int y, out int result, out int remainder) {
result = x / y;
remainder = x % y;
}

static void Main() {
int res, rem;
Divide(10, 3, out res, out rem);
Console.WriteLine("{0} {1}", res, rem); // Результат: "3 1"
}
}

Массив параметров используется для передачи методу переменного числа аргументов. Массив параметров объявляется с помощью модификатора params. В качестве массива параметров может использоваться только последний параметр метода. Массив параметров должен являться одномерным массивом. Методы Write и WriteLine класса System.Console являются примерами массивов параметров. Эти методы объявляются следующим образом.

public class Console
{
public static void Write(string fmt, params object[] args) {...}

public static void WriteLine(string fmt, params object[] args) {...}

...
}

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

Console.WriteLine("x={0} y={1} z={2}", x, y, z);

равнозначен следующей записи:

string s = "x={0} y={1} z={2}";
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

1.6.6.2 Тело метода и локальные переменные

В теле метода определяются операторы, выполняемые при его вызове.

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

using System;

class Squares
{
static void Main() {
int i = 0;
int j;
while (i < 10) {
j = i * i;
Console.WriteLine("{0} x {0} = {1}", i, j);
i = i + 1;
}
}
}

В C# значение локальной переменной можно использовать только после явного присваивания ей значения. Например, если объявлению i (см. выше) не присвоено начальное значение, при последующем его использовании возникнет ошибка компиляции, поскольку в момент использования переменной i ей явно не присвоено значение.

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

1.6.6.3 Статические методы и методы экземпляра

Метод, объявленный с использованием модификатора static, называется статическим методом. Статический метод не выполняет операций с конкретным экземпляром и может только напрямую обращаться к статическим членам.

Метод, объявленный без использования модификатора static, называется методом экземпляра. Метод экземпляра выполняет операции только с конкретным экземпляром и может обращаться как к статическим членам, так и к членам экземпляра. Явное обращение к экземпляру, для которого вызывается метод экземпляра, выполняется с помощью зарезервированного слова this. При ссылке на объект this статического метода возникает ошибка.

В следующем примере класс Entity содержит как статические члены, так и члены экземпляра.

class Entity
{
static int nextSerialNo;

int serialNo;

public Entity() {
serialNo = nextSerialNo++;
}

public int GetSerialNo() {
return serialNo;
}

public static int GetNextSerialNo() {
return nextSerialNo;
}

public static void SetNextSerialNo(int value) {
nextSerialNo = value;
}
}

Каждый экземпляр класса Entity содержит серийный номер (и, возможно, другие данные, не показанные в примере). Конструктор класса Entity (аналогичен методу экземпляра) инициализирует новый экземпляр со следующим доступным серийным номером. Поскольку конструктор представляет собой член экземпляра, допускается обращение как к полю экземпляра serialNo, так и к статическому полю nextSerialNo.

Статические методы GetNextSerialNo и SetNextSerialNo могут обращаться к статическому полю nextSerialNo, однако при непосредственном обращении этих методов к полю экземпляра serialNo возникнет ошибка.

В следующем примере показано использование класса Entity.

using System;

class Test
{
static void Main() {
Entity.SetNextSerialNo(1000);

Entity e1 = new Entity();
Entity e2 = new Entity();

Console.WriteLine(e1.GetSerialNo()); // Результат: "1000"
Console.WriteLine(e2.GetSerialNo()); // Результат: "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Результат: "1002"
}
}

Обратите внимание, что статические методы SetNextSerialNo и GetNextSerialNo вызываются для класса, а метод экземпляра GetSerialNo — для экземпляра класса.

1.6.6.4 Виртуальные, переопределяющие и абстрактные методы

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

При вызове виртуального метода тип времени выполнения экземпляра, для которого осуществляется вызов, определяет фактическую реализацию вызываемого метода. При вызове невиртуального метода определяющим фактором является тип времени компиляции экземпляра.

Виртуальный метод может быть переопределен в производном классе. Если объявление метода экземпляра содержит модификатор override, метод переопределяет унаследованный виртуальный метод с такой же подписью. Объявление виртуального метода определяет новый метод. Объявление переопределяющего метода уточняет существующий виртуальный метод, предоставляя его новую реализацию.

Абстрактным называется виртуальный метод без реализации. Объявление абстрактного метода осуществляется с использованием модификатора abstract и допускается только в классе, объявленном как abstract. В каждом неабстрактном производном классе необходимо переопределять абстрактный метод.

В следующем примере объявляется абстрактный класс Expression, представляющий узел дерева выражений, а также три производных класса: Constant, VariableReference и Operation, которые реализуют узлы дерева выражений для констант, ссылок на переменные и арифметических операций. (Эти классы похожи на типы дерева выражений, представленные в разделе §4.6. Однако их не следует путать.)

using System;
using System.Collections;

public abstract class Expression
{
public abstract double Evaluate(Hashtable vars);
}

public class Constant: Expression
{
double value;

public Constant(double value) {
this.value = value;
}

public override double Evaluate(Hashtable vars) {
return value;
}
}

public class VariableReference: Expression
{
string name;

public VariableReference(string name) {
this.name = name;
}

public override double Evaluate(Hashtable vars) {
object value = vars[name];
if (value == null) {
throw new Exception("Неизвестная переменная: " + name);
}
return Convert.ToDouble(value);
}
}

public class Operation: Expression
{
Expression left;
char op;
Expression right;

public Operation(Expression left, char op, Expression right) {
this.left = left;
this.op = op;
this.right = right;
}

public override double Evaluate(Hashtable vars) {
double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch (op) {
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Неизвестный оператор");
}
}

Четыре приведенных выше класса могут использоваться для моделирования арифметических выражений. Например, с помощью экземпляров этих классов выражение x + 3 можно представить следующим образом.

Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));

Метод Evaluate экземпляра Expression вызывается для вычисления заданного выражения и возвращает значение типа double. Метод принимает в качестве аргумента параметр Hashtable, содержащий имена переменных (в качестве ключей записей) и значения (в качестве значений записей). Метод Evaluate представляет собой виртуальный абстрактный метод. Это означает, что в производных от него неабстрактных классах необходимо переопределить этот метод и предоставить его фактическую реализацию.

Реализация Constant метода Evaluate возвращает хранящееся значение константы. Реализация VariableReference осуществляет поиск имени переменной в хэш-таблице и возвращает значение результата. Реализация Operation сначала выполняет вычисление левого и правого операндов (посредством рекурсивного вызова соответствующих методов Evaluate), а затем выполняет заданную арифметическую операцию.

В следующей программе классы Expression используются для вычисления выражения x * (y + 2) с различными значениями x и y.

using System;
using System.Collections;

class Test
{
static void Main() {

Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);

Hashtable vars = new Hashtable();

vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // Результат: "21"

vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // Результат: "16.5"
}
}

1.6.6.5 Перегрузка метода

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

class Test
{
static void F() {
Console.WriteLine("F()");
}

static void F(object x) {
Console.WriteLine("F(object)");
}

static void F(int x) {
Console.WriteLine("F(int)");
}

static void F(double x) {
Console.WriteLine("F(double)");
}

static void F<T>(T x) {
Console.WriteLine("F<T>(T)");
}

static void F(double x, double y) {
Console.WriteLine("F(double, double)");
}

static void Main() {
F(); // Вызывается F()
F(1); // Вызывается F(int)
F(1.0); // Вызывается F(double)
F("abc"); // Вызывается F(object)
F((double)1); // Вызывается F(double)
F((object)1); // Вызывается F(object)
F<int>(1); // Вызывается F<T>(T)
F(1, 1); // Вызывается F(double, double) }
}

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

1.6.7 Другие функции-члены

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

В следующей таблице описывается универсальный класс List<T>, который реализует расширяемый список объектов. Класс содержит несколько примеров наиболее распространенных типов функций-членов.

public class List<T> {
const int defaultCapacity = 4; Константа
T[] items; int count; Поля
public List(): this(defaultCapacity) {} public List(int capacity) { items = new T[capacity]; } Конструкторы
public int Count { get { return count; } } public int Capacity { get { return items.Length; } set { if (value < count) value = count; if (value != items.Length) { T[] newItems = new T[value]; Array.Copy(items, 0, newItems, 0, count); items = newItems; } } } Свойства

 

public T this[int index] { get { return items[index]; } set { items[index] = value; OnChanged(); } } Индексатор
public void Add(T item) { if (count == Capacity) Capacity = count * 2; items[count] = item; count++; OnChanged(); } protected virtual void OnChanged() { if (Changed != null) Changed(this, EventArgs.Empty); } public override bool Equals(object other) { return Equals(this, other as List<T>); } static bool Equals(List<T> a, List<T> b) { if (a == null) return b == null; if (b == null || a.count != b.count) return false; for (int i = 0; i < a.count; i++) { if (!object.Equals(a.items[i], b.items[i])) { return false; } } return true; } Методы
public event EventHandler Changed; Событие
public static bool operator ==(List<T> a, List<T> b) { return Equals(a, b); } public static bool operator !=(List<T> a, List<T> b) { return !Equals(a, b); } Операторы
}

 

1.6.7.1 Конструкторы

В C# поддерживаются конструкторы экземпляров и статические конструкторы. Конструктор экземпляра представляет собой член, который реализует действие, необходимое для инициализации экземпляра класса. Статический конструктор представляет собой член, который реализует действие, необходимое для инициализации самого класса при его первой загрузке.

Конструктор объявляется аналогично методу без типа возвращаемого значения и имеет то же имя, что и содержащий его класс. Объявление конструктора, содержащее модификатор static, объявляет статический конструктор. В противном случае объявляется конструктор экземпляра.

Конструкторы экземпляров можно перегружать. Например, в классе List<T> объявляются два конструктора: один без параметров и один, принимающий параметр типа int. Вызов конструктора экземпляра осуществляется с помощью оператора new. В следующем примере выделяются два экземпляра List<string> с использованием каждого из конструкторов класса List.

List<string> list1 = new List<string>();
List<string> list2 = new List<string>(10);

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

1.6.7.2 Свойства

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

Свойство объявляется аналогично полю, однако объявление свойства должно заканчиваться не точкой с запятой, а методами доступа get или set, записанными между разделителями — { и }. Свойство, для которого определены оба метода доступа get и set, называется свойством для чтения и записи. Свойство, для которого определен только метод доступа get, называется свойством только для чтения. Свойство, для которого определен только метод доступа set, называется свойством только для записи.

Метод доступа get соответствует не содержащему параметров методу, возвращаемое значение которого имеет тип свойства. За исключением случаев, когда свойство является конечным объектом операции присваивания, при ссылке на свойство в выражении вызывается метод доступа get для вычисления значения свойства.

Метод доступа set соответствует методу с одним параметром value, не имеющему типа возвращаемого значения. При ссылке на свойство как на конечный объект операции присваивания или как на операнд операторов «++» и «--» метод доступа set вызывается с аргументом, который предоставляет новое значение.

В классе List<T> объявляются два свойства: Count и Capacity (только для чтения и только для записи соответственно). В следующем примере показано использование этих свойств.

List<string> names = new List<string>();
names.Capacity = 100; // Вызывается метод доступа set
int i = names.Count; // Вызывается метод доступа get
int j = names.Capacity; // Вызывается метод доступа get

Как и в случае с полями и методами, в C# поддерживаются свойства экземпляров и статические свойства. Свойства, объявленные с использованием модификатора static, называются статическими. Все остальные свойства называются свойствами экземпляров.

Методы доступа свойства могут быть виртуальными. Если объявление свойства содержит модификатор virtual, abstract или override, соответствующий тип применяется и к его методам доступа.

1.6.7.3 Индексаторы

Индексатор — это член, предназначенный для индексации объектов (аналогично массивам). Индексатор объявляется аналогично свойству, однако в качестве имени члена используется this, за которым следует список параметров, записанный между разделителями — [ и ]. Параметры доступны в методах доступа индексатора. Как и свойства, индексаторы могут быть предназначены только для чтения, только для записи или для чтения и записи. Методы доступа индексатора могут быть виртуальными.

В классе List объявляется один индексатор для чтения и записи, принимающий параметр int. С его помощью обеспечивается индексация экземпляров класса List с использованием значений типа int. Пример:

List<string> names = new List<string>();
names.Add("Анна");
names.Add("Екатерина");
names.Add("Светлана");
for (int i = 0; i < names.Count; i++) {
string s = names[i];
names[i] = s.ToUpper();
}

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

1.6.7.4 События

Событие — это член, используемый классом или объектом для уведомления. Событие объявляется аналогично полю, однако оно должно иметь тип делегата и его объявление должно содержать зарезервированное слово event.

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

В классе List<T> объявляется член события Changed, указывающий на добавление нового элемента в список. Событие Changed вызывается виртуальным методом OnChanged, в котором сначала проверяется, имеет ли событие значение null (т. е. для события отсутствуют обработчики). Понятие вызова события в точности соответствует вызову делегата, представленного этим событием. Поэтому в языке не предусмотрены специальные конструкции для вызова событий.

Реакция клиента на событие реализуется с помощью обработчиков событий. Для вложения обработчиков событий используется оператор «+=», для удаления — оператор «-=». В следующем примере к событию Changed класса List<string> присоединяется обработчик событий.

using System;

class Test
{
static int changeCount;

static void ListChanged(object sender, EventArgs e) {
changeCount++;
}

static void Main() {
List<string> names = new List<string>();
names.Changed += new EventHandler(ListChanged);
names.Add("Анна");
names.Add("Екатерина");
names.Add("Светлана");
Console.WriteLine(changeCount); // Результат: "3"
}
}

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

1.6.7.5 Операторы

Оператор — это член, который определяет значение применения конкретного оператора выражения к экземплярам класса. Поддерживается определение операторов трех видов: унарные операторы, двоичные операторы и операторы преобразования. Все операторы должны объявляться с использованием модификаторов public и static.

В классе List<T> объявляются два оператора: «==» и «!=», что позволяет определить новое значение для выражений, которые применяют эти операторы к экземплярам класса List. В частности, операторы определяют равенство двух экземпляров класса List<T> посредством сравнения всех содержащихся в них объектов с помощью методов Equals. В следующем примере оператор «==» используется для сравнения двух экземпляров класса List<int>.

using System;

class Test
{
static void Main() {
List<int> a = new List<int>();
a.Add(1);
a.Add(2);
List<int> b = new List<int>();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); // Результат: "True"
b.Add(3);
Console.WriteLine(a == b); // Результат: "False"
}
}

Первое выражение Console.WriteLine возвращает True, поскольку два списка содержат одинаковое число объектов с одинаковыми значениями и в одинаковом порядке. Если бы в классе List<T> не был определен оператор «==», первое выражение Console.WriteLine возвращало бы False, поскольку a и b ссылаются на различные экземпляры класса List<int>.

1.6.7.6 Деструкторы

Деструктор представляет собой член, который реализует действие, необходимое для уничтожения экземпляра класса. Деструкторы не могут иметь параметров или модификаторов доступа и не могут быть вызваны явно. Деструктор экземпляра вызывается автоматически в процессе сборки мусора.

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

Наиболее эффективный подход к уничтожению объектов обеспечивается при использовании оператора using.

1.7 Структуры

Как и классы, структуры представляют собой структуры данных, содержащие данные-члены и функции-члены. Однако, в отличие от классов, структуры имеют тип значений и не требуют выделения памяти в куче. Переменная типа структуры непосредственно хранит данные структуры, тогда как переменная типа класса хранит ссылку на динамически выделяемый объект. Типы структуры не поддерживают определяемое пользователем наследование. Все типы структуры неявно наследуются от типа object.

Структуры особенно эффективны при работе с небольшими структурами данных, обладающими семантикой значений. Примерами структур являются комплексные числа, точки в системе координат или словарные пары «ключ-значение». Применение структур вместо классов при работе с небольшими структурами данных позволяет добиться значительной экономии выделяемой памяти и повышения производительности приложения. Например, в следующей программе создается и инициализируется массив из 100 точек. Если в качестве класса реализуется Point, создается 101 экземпляр объектов — один для массива и по одному для каждого из 100 его элементов.

class Point
{
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

class Test
{
static void Main() {
Point[] points = new Point[100];
for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
}
}

Также можно реализовать Point как структуру.

struct Point
{
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

В этом случае создается только один экземпляр объекта для массива. Экземпляры Point хранятся встроенными в массив.

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

Две переменные типа класса могут ссылаться на один объект. Это позволяет изменять объект, на который ссылается одна переменная, выполняя соответствующие операции с другой. Каждая переменная типа структуры содержит собственную копию данных. В связи с этим операции с одной переменной не влияют на другую. Например, выходные данные следующего фрагмента кода зависят от того, является ли Point классом или структурой.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Если Point является классом, возвращается значение 20, поскольку a и b ссылаются на один объект. Если Point является структурой, возвращается значение 10, поскольку при присваивании a экземпляру b создается копия значения, на которую не влияет последующее присваивание a.x.

В предыдущем примере показаны два ограничения, накладываемые на структуры. Во-первых, копирование всей структуры обычно менее эффективно, чем копирование ссылки на объект, из-за чего операции присваивания и передачи параметров значения для структур потребляют больше ресурсов, чем аналогичные операции для ссылочных типов. Во-вторых, не поддерживается создание ссылок на структуры (за исключением параметров ref и out), что в некоторых случаях не позволяет использовать структуры.

1.8 Массивы

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

Типы массивов являются ссылочными типами, поэтому при объявлении переменной типа массива выделяется память для ссылки на экземпляр массива. Фактические экземпляры массива создаются динамически во время выполнения с помощью оператора new. Оператор new определяет длину создаваемого экземпляра массива, которая остается неизменной в течение всего времени существования экземпляра. Элементам массива присваиваются индексы в диапазоне от 0 до Length - 1. Оператор new автоматически инициализирует элементы массива с использованием значений по умолчанию (нули для всех числовых типов или null для всех ссылочных типов).

В следующем примере создается и инициализируется массив элементов типа int, после чего выводится содержимое созданного массива.

using System;

class Test
{
static void Main() {
int[] a = new int[10];
for (int i = 0; i < a.Length; i++) {
a[i] = i * i;
}
for (int i = 0; i < a.Length; i++) {
Console.WriteLine("a[{0}] = {1}", i, a[i]);
}
}
}

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

int[] a1 = new int[10];
int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];

Массив a1 содержит 10 элементов, массив a2 — 50 (10 Ч 5) элементов, а массив a3 — 100 (10 Ч 5 Ч 2) элементов.

Элементы массива могут иметь любой тип (в том числе и тип массива). Массив, содержащий элементы типа массива (jagged array, или массив массивов), иногда называется неравномерным массивом, поскольку его элементы могут иметь различную длину. В следующем примере выделяется память для неравномерного массива типа int:

int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];

В первой строке создается массив, состоящий из трех элементов типа int[], каждый из которых имеет начальное значение null. В последующих строках инициализируются три элемента со ссылками на отдельные экземпляры массивов различной длины.

Оператор new позволяет задать начальные значения элементов массива с помощью инициализатора массива, который представляет собой список выражений, записанных между разделителями { и }. В следующем примере выделяется и инициализируется массив int[], содержащий три элемента.

int[] a = new int[] {1, 2, 3};

Обратите внимание, что длина массива определяется на основании числа выражений между фигурными скобками { и }. Поддерживается сокращенная форма объявления локальных переменных и полей, что позволяет не указывать повторно тип массива.

int[] a = {1, 2, 3};

Оба предыдущих примера эквивалентны следующему:

int[] t = new int[3];
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;

1.9 Интерфейсы

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

Интерфейсы могут применять множественное наследование. В следующем примере интерфейс IComboBox одновременно наследуется от интерфейсов ITextBox и IListBox.

interface IControl
{
void Paint();
}

interface ITextBox: IControl
{
void SetText(string text);
}

interface IListBox: IControl
{
void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

Классы и структуры способны реализовывать несколько интерфейсов. В следующем примере класс EditBox реализует одновременно интерфейсы IControl и IDataBound.

interface IDataBound
{
void Bind(Binder b);
}

public class EditBox: IControl, IDataBound
{
public void Paint() {...}

public void Bind(Binder b) {...}
}

Если класс или структура реализует конкретный интерфейс, экземпляры такого класса или такой структуры могут быть неявно преобразованы к типу заданного интерфейса. Пример:

EditBox editBox = new EditBox();
IControl control = editBox;
IDataBound dataBound = editBox;

Если статически неизвестно, реализует ли экземпляр конкретный интерфейс, могут использоваться динамические приведения типов. Например, следующие операторы используют динамические приведения типов для получения реализаций интерфейсов IControl и IDataBound объекта. Поскольку фактическим типом объекта является EditBox, приведения выполняются успешно.

object obj = new EditBox();
IControl control = (IControl)obj;
IDataBound dataBound = (IDataBound)obj;

Для описанного выше класса EditBox метод Paint интерфейса IControl и метод Bind интерфейса IDataBound реализуются с использованием членов public. В C# также поддерживается явная реализация членов интерфейса, что позволяет не использовать в классах и структурах члены public. Явная реализация члена интерфейса записывается с использованием полного имени члена интерфейса. Например, класс EditBox может использовать явные реализации членов интерфейса для реализации методов IControl.Paint и IDataBound.Bind следующим образом.

public class EditBox: IControl, IDataBound
{
void IControl.Paint() {...}

void IDataBound.Bind(Binder b) {...}
}

Обращение к явным членам интерфейса осуществляется исключительно посредством типа интерфейса. Например, реализация метода IControl.Paint, предоставляемая описанным выше классом EditBox, может быть вызвана только с предварительным преобразованием ссылки на EditBox к типу интерфейса IControl.

EditBox editBox = new EditBox();
editBox.Paint(); // Ошибка: такого метода не существует
IControl control = editBox;
control.Paint(); // Допустимо

1.10 Перечисляемые типы

Перечисляемый тип представляет собой тип значений, содержащий конечное число именованных констант. В следующем примере объявляется и используется перечисляемый тип Color, содержащий три постоянных значения: Red, Green и Blue.

using System;

enum Color
{
Red,
Green,
Blue
}

class Test
{
static void PrintColor(Color color) {
switch (color) {
case Color.Red:
Console.WriteLine("Красный");
break;
case Color.Green:
Console.WriteLine("Зеленый");
break;
case Color.Blue:
Console.WriteLine("Синий");
break;
default:
Console.WriteLine("Неизвестный цвет");
break;
}
}

static void Main() {
Color c = Color.Red;
PrintColor(c);
PrintColor(Color.Blue);
}
}

Каждому перечисляемому типу соответствует целый тип, называемый базовым типом. Если для перечисляемого типа явно не объявлен базовый тип, в качестве базового используется тип int. Формат хранения и диапазон возможных значений перечисляемого типа определяются его базовым типом. Набор значений перечисляемого типа может включать его члены, но не ограничивается только ими. В частности, любое значение базового типа может быть приведено к перечисляемому типу и является отдельным допустимым значением такого типа.

В следующем примере объявляется перечисляемый тип Alignment, базовым для которого является тип sbyte.

enum Alignment: sbyte
{
Left = -1,
Center = 0,
Right = 1
}

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

Значения перечисляемых типов могут быть преобразованы к значениям целых типов (и наоборот) с помощью приведений типов. Пример:

int i = (int)Color.Blue; // int i = 2;
Color c = (Color)2; // Color c = Color.Blue;

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

Color c = 0;

1.11 Делегаты

Тип делегата представляет собой ссылки на методы с конкретным списком параметров и типом возвращаемого значения. С помощью делегатов методы обрабатываются как сущности, которым можно передавать параметры и которые можно присваивать переменным. Понятие делегата близко к понятию указателя на функцию, используемому в некоторых других языках. Однако делегаты, в отличие от указателей на функции, представляют собой пример объектно-ориентированного и строго типизированного подхода к программированию.

В следующем примере объявляется и используется тип делегата Function.

using System;

delegate double Function(double x);

class Multiplier
{
double factor;

public Multiplier(double factor) {
this.factor = factor;
}

public double Multiply(double x) {
return x * factor;
}
}

class Test
{
static double Square(double x) {
return x * x;
}

static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}

static void Main() {
double[] a = {0.0, 0.5, 1.0};

double[] squares = Apply(a, Square);

double[] sines = Apply(a, Math.Sin);

Multiplier m = new Multiplier(2.0);
double[] doubles = Apply(a, m.Multiply);
}
}

Экземпляр типа делегата Function может ссылаться на любой метод, который принимает аргумент и возвращает значение типа double. Метод Apply применяет заданную функцию Function к элементам массива double[] и возвращает массив double[], содержащий результаты. В методе Main метод Apply используется для применения трех различных функций к массиву double[].

Делегат может ссылаться как на статический метод (например, Square или Math.Sin в предыдущем примере), так и на метод экземпляра (например, m.Multiply в предыдущем примере). Делегат, ссылающийся на метод экземпляра, также ссылается на конкретный объект. При вызове такого метода экземпляра с помощью делегата этот объект становится объектом this.

Делегаты также могут создаваться с помощью анонимных функций, которые представляют собой «встроенные методы», создаваемые в процессе выполнения. Анонимные функции могут видеть локальные переменные окружающих их методов. Таким образом, приведенный выше пример множителя может быть записан проще с использованием класса Multiplier:

double[] doubles = Apply(a, (double x) => x * 2.0);

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

1.12 Атрибуты

Типы, члены и другие сущности C# поддерживают модификаторы, которые управляют определенными аспектами их поведения. Например, доступность метода управляется с помощью модификаторов public, protected, internal и private. Благодаря этой возможности в C# пользовательские типы декларативных сведений могут быть вложены в сущности программы и извлекаться во время выполнения. Такие дополнительные декларативные сведения задаются в программе посредством определения и использования атрибутов.

В следующем примере атрибут HelpAttribute присоединяется к сущностям программы и предоставляет ссылки на связанную с ними документацию.

using System;

public class HelpAttribute: Attribute
{
string url;
string topic;

public HelpAttribute(string url) {
this.url = url;
}

public string Url {
get { return url; }
}

public string Topic {
get { return topic; }
set { topic = value; }
}
}

Все классы атрибутов наследуются от базового класса System.Attribute, предоставляемого платформой .NET Framework. Чтобы применить атрибут, необходимо указать его имя и любые другие аргументы в квадратных скобках непосредственно перед связанным объявлением. Если имя атрибута заканчивается словом Attribute, при ссылке на него эту часть имени можно опустить. Например, атрибут HelpAttribute можно использовать следующим образом.

[Help("http://msdn.microsoft.com/.../MyClass.htm")]
public class Widget
{
[Help("http://msdn.microsoft.com/.../MyClass.htm", Topic = "Display")]
public void Display(string text) {}
}

В этом примере атрибут HelpAttribute присоединяется к классу Widget, а другой атрибут HelpAttribute — к методу Display класса. Общие конструкторы класса атрибута управляют сведениями, которые предоставляются при вложении атрибута в сущность программы. Дополнительные сведения предоставляются посредством ссылки на открытые свойства для чтения и записи класса атрибута (например, ссылка на свойства Topic в предыдущем примере).

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

using System;
using System.Reflection;

class Test
{
static void ShowHelp(MemberInfo member) {
HelpAttribute a = Attribute.GetCustomAttribute(member,
typeof(HelpAttribute)) as HelpAttribute;
if (a == null) {
Console.WriteLine("Отсутствует справка по {0}", member);
}
else {
Console.WriteLine("Справка по {0}:", member);
Console.WriteLine(" Url={0}, Topic={1}", a.Url, a.Topic);
}
}

static void Main() {
ShowHelp(typeof(Widget));
ShowHelp(typeof(Widget).GetMethod("Display"));
}
}

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


2. Лексическая структура

2.1 Программы

Программа на C# состоит из одного или более исходных файлов, формально называемых единицами компиляции (§9.1). Исходный файл — это упорядоченная последовательность символов Юникода. Исходные файлы обычно взаимнооднозначно соответствуют файлам файловой системы, но это соответствие не является обязательным. Для максимальной переносимости рекомендуется использовать для файлов в файловой системе кодировку UTF-8.

С концептуальной точки зрения программа компилируется в три этапа:

· Преобразование: файл преобразуется из конкретного набора символов и схемы кодировки в последовательность символов Юникода.

· Лексический анализ: поток входных символов Юникода преобразуется в поток лексем.

· Синтаксический анализ: поток лексем преобразуется в исполняемый код.

2.2 Грамматики

В настоящей спецификации представлен синтаксис языка программирования C#, использующий две грамматики. Лексическая грамматика (§2.2.2) определяет, как объединяются символы Юникода для образования признаков конца строки, пробелов, комментариев, лексем и препроцессорных директив. Синтаксическая грамматика (§2.2.3) определяет, как объединяются лексемы, полученные от лексической грамматики, для образования программ на C#.

2.2.1 Грамматическая нотация

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

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

оператор_while:
while ( логическое_выражение ) внедренный_оператор

определяет оператор_while как состоящий из лексемы while, последующей лексемы «(», последующего логического_выражения, последующей лексемы «)» и последующего внедренного_оператора.

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

список_операторов:
оператор
список_операторов оператор

определяет список_операторов как состоящий или из оператора, или из списка_операторов с последующим оператором. Иначе говоря, это определение является рекурсивным и указывает, что список операторов состоит из одного или более операторов.

Подстрочный суффикс «необ» используется для указания необязательного символа. Порождение:

блок:
{ список_операторовнеоб }

является краткой записью для:

блок:
{ }
{ список_операторов }

и определяет блок как состоящий из необязательного списка_операторов, заключенного в лексемы «{» и «}».

Варианты обычно перечисляются в отдельных строках, но если вариантов много, фраза «one of» («одно из») может предшествовать списку подстановок, заданных в одной строке. Это просто краткая запись вместо перечисления каждого из вариантов отдельной строкой. Например, порождение:

суффикс_действительного_типа: одно из
F f D d M m

является краткой записью для:

суффикс_действительного_типа:
F
f
D
d
M
m

2.2.2 Лексическая грамматика

Лексическая грамматика C# представлена в §2.3, §2.4 и §2.5. Терминальными символами лексической грамматики являются символы набора символов Юникода. Лексическая грамматика определяет, как символы объединяются для образования лексем (§2.4), пробелов (§2.3.3), комментариев (§2.3.2) и препроцессорных директив (§2.5).

Каждый исходный файл программы на C# должен соответствовать порождению ввода лексической грамматики (§2.3).

2.2.3 Синтаксическая грамматика

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

Каждый исходный файл в программе на C# должен соответствовать порождению единицы_компиляции синтаксической грамматики (§9.1).

2.3 Лексический анализ

Порождение ввода определяет лексическую структуру исходного файла на C#. Каждый исходный файл в программе на C# должен соответствовать этому порождению лексической грамматики.

ввод:
раздел_вводанеоб

раздел_ввода:
часть_раздела_ввода
раздел_ввода часть_раздела_ввода

часть_раздела_ввода:
элементы_вводанеоб новая_строка
ПРО_директива

элементы_ввода:
элемент_ввода
элементы_ввода элемент_ввода

элемент_ввода:
пробел
комментарий
лексема

Лексическую структуру исходного файла на C# составляют пять основных элементов: признаки конца строки (§2.3.1), пробелы (§2.3.3), комментарии (§2.3.2), лексемы (§2.4) и препроцессорные директивы (§2.5). Из этих основных элементов только лексемы являются значимыми в синтаксической грамматике программы на C# (§2.2.3).

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

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

2.3.1 Признаки конца строки

Признаки конца строки разделяют на строки символы исходного файла на C#.

новая_строка:
символ возврата каретки (U+000D)
символ перевода строки (U+000A)
символ возврата каретки (U+000D) с последующим символом перевода строки (U+000A)
символ следующей строки (U+0085)
символ разделителя строк (U+2028)
символ разделителя абзацев (U+2029)

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

· если последним символом исходного файла является символ CTRL-Z (U+001A), этот символ удаляется;

· символ возврата каретки (U+000D) добавляется к концу исходного файла, если файл не пустой и если последним символом исходного файла не является символ возврата каретки (U+000D), символ перевода строки (U+000A), символ разделителя строк (U+2028) или символ разделителя абзацев (U+2029).

2.3.2 Комментарии

Поддерживается две формы комментариев: однострочные комментарии и комментарии с разделителями. Однострочные комментарии начинаются с символов // и продолжаются до конца исходной строки. Комментарии с разделителями начинаются с символов /* и заканчиваются символами */. Комментарии с разделителями могут занимать несколько строк.

комментарий:
однострочный_комментарий
комментарий_с_разделителями

однострочный_комментарий:
// символы_вводанеоб

символы_ввода:
символ_ввода
символы_ввода символ_ввода

символ_ввода:
любой символ Юникода, кроме символа_новой_строки

символ_новой_строки:
символ возврата каретки (U+000D)
символ перевода строки (U+000A)
символ следующей строки (U+0085)
символ разделителя строк (U+2028)
символ разделителя абзацев (U+2029)

комментарий_с_разделителями:
/* текст_комментария_с_разделителяминеоб звездочки /

текст_комментария_с_разделителями:
раздел_комментария_с_разделителями
текст_комментария_с_разделителями раздел_комментария_с_разделителями

раздел_комментария_с_разделителями:
/
звездочкинеоб не_косая_черта_и_не_звездочка

звездочки:
*
звездочки *

не_косая_черта_и_не_звездочка:
любой символ Юникода, кроме / или *

Комментарии не могут быть вложенными. Последовательности символов /* и */ не имеют специального значения внутри комментария //, а последовательности символов // и /* не имеют специального значения внутри комментария с разделителями.

Комментарии не обрабатываются внутри символьных и строковых литералов.

Пример

/* Программа Hello, world
Эта программа выводит на консоль «hello, world»
*/
class Hello
{
static void Main() {
System.Console.WriteLine("hello, world");
}
}

содержит комментарий с разделителями.

В примере

// Программа Hello, world
// Эта программа выводит на консоль “hello, world”
//
class Hello // для этого класса годится любое имя
{
static void Main() { // этот метод должен называться "Main"
System.Console.WriteLine("hello, world");
}
}

показано несколько однострочных комментариев.

2.3.3 Пробел

Пробел определяется как любой символ Юникода класса Zs (который включает символ пробела), а также как символ горизонтальной табуляции, символ вертикальной табуляции и символ перевода страницы.

пробел:
любой символ Юникода класса Zs
символ горизонтальной табуляции (U+0009)
символ вертикальной табуляции (U+000B)
символ перевода страницы (U+000C)

2.4 Лексемы

Существует несколько видов лексем: идентификаторы, зарезервированные слова, литералы, операторы и знаки пунктуации. Пробел и комментарии не являются лексемами, хотя действуют как разделители для лексем.

лексема:
идентификатор
зарезервированное_слово
целочисленный_литерал
действительный_литерал
символьный_литерал
строковый_литерал
оператор_или_знак_пунктуации

2.4.1 Управляющие последовательности символов Юникода

Управляющая последовательность символов Юникода представляет собой символ Юникода. Управляющие последовательности символов Юникода обрабатываются в идентификаторах (§2.4.2), символьных литералах (§2.4.4.4) и правильных строковых литералах (§2.4.4.5). Управляющая последовательность символов Юникода не обрабатывается в других местах (например, для образования оператора, знака пунктуации или зарезервированного слова).

управляющая_последовательность_Юникода:
\u 16_р_циф 16_р_циф 16_р_циф 16_р_циф
\U 16_р_циф 16_р_циф 16_р_циф 16_р_циф 16_р_циф 16_р_циф 16_р_циф 16_р_циф

Управляющая последовательность Юникода (escape-последовательность Юникода) представляет собой один символ Юникода, образованный шестнадцатеричным числом, следующим за символами «\u» или «\U». Поскольку в C# используется 16-разрядная кодировка элементов кода Юникода в символьных и строковых значениях, символ Юникода в диапазоне от U+10000 до U+10FFFF запрещен в строковом литерале и представляется с помощью суррогатной пары Юникода в строковом литерале. Символы Юникода с элементами кода выше 0x10FFFF не поддерживаются.

Многократные трансляции не выполняются. Например, строковый литерал «\u005Cu005C» эквивалентен «\u005C», а не «\». Значение Юникода \u005C является символом «\».

В примере

class Class1
{
static void Test(bool \u0066) {
char c = '\u0066';
if (\u0066)
System.Console.WriteLine(c.ToString());
}
}

показано несколько использований \u0066, escape-последовательности для буквы «f». Эта программа эквивалентна следующей:

class Class1
{
static void Test(bool f) {
char c = 'f';
if (f)
System.Console.WriteLine(c.ToString());
}
}

2.4.2 Идентификаторы

Правила для идентификаторов в этом разделе точно соответствуют правилам, рекомендованным в дополнении 15 к стандарту Юникода, за исключением следующего: знак подчеркивания разрешен в качестве начального символа (что традиционно для языка программирования C), escape-последовательности Юникода разрешены в идентификаторах, а символ «@» разрешен в качестве префикса, чтобы можно было использовать зарезервированные слова в качестве идентификаторов.

идентификатор:
доступный_идентификатор
@ идентификатор_или_зарезервированное_слово

доступный_идентификатор:
идентификатор_или_зарезервированное_слово, не являющийся зарезервированным_словом

идентификатор_или_зарезервированное_слово:
начальный_символ_идентификатора символы_части_идентификаторанеоб

начальный_символ_идентификатора:
символ_буквы
_ (символ подчеркивания U+005F)

символы_части_идентификатора:
символ_части_идентификатора
символы_части_идентификатора символ_части_идентификатора

символ_части_идентификатора:
символ_буквы
десятичная_цифра
символ_соединения
несамостоятельный_символ
символ_управления_форматом

символ_буквы:
символ Юникода классов Lu, Ll, Lt, Lm, Lo или Nl
управляющая_последовательность_Юникода, представляющая символ классов Lu, Ll, Lt, Lm, Lo или Nl

несамостоятельный_символ:
символ Юникода класса Mn или Mc
управляющая_последовательность_Юникода, представляющая символ класса Mn или Mc

десятичная_цифра:
символ Юникода класса Nd
управляющая_последовательность_Юникода, представляющая символ класса Nd

символ_соединения:
символ Юникода класса Pc
управляющая_последовательность_Юникода, представляющая символ класса Pc

символ_управления_форматом:
символ Юникода класса Cf
управляющая_последовательность_Юникода, представляющая символ класса Cf

Дополнительные сведения об упомянутых выше классах символов Юникода см. в документе Стандарт Юникода, версия 3.0, раздел 4.5.

Примеры допустимых идентификаторов: «identifier1», «_identifier2», «@if».

Идентификатор в соответствующей программе должен быть в каноническом формате, определенном формой нормализации C Юникода, как определено в дополнении 15 к стандарту Юникода. Поведение при обнаружении идентификатора не в форме нормализации C определяется реализацией, однако диагностика при этом не требуется.

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

В примере

class @class
{
public static void @static(bool @bool) {
if (@bool)
System.Console.WriteLine("true");
else
System.Console.WriteLine("false");
}
}

class Class1
{
static void M() {
cl\u0061ss.st\u0061tic(true);
}
}

определен класс с именем «class» со статическим методом с именем «static», принимающим параметр с именем «bool». Обратите внимание, что, поскольку управляющие последовательности Юникода не разрешены в зарезервированных словах, лексема «cl\u0061ss» является идентификатором, причем тем же идентификатором, что и «@class».

Два идентификатора считаются одинаковыми, если они идентичны после применения следующих преобразований в таком порядке:

· если используется префикс “@”, он удаляется;

· каждая последовательность_управляющих_символов_Юникода преобразуется в соответствующий символ Юникода;

· все символы_управления_форматом удаляются.

Идентификаторы, содержащие два последовательных символа подчеркивания (U+005F), зарезервированы для использования в реализации. Например, реализация может предоставлять расширенные зарезервированные слова, начинающиеся с двух символов подчеркивания.

2.4.3 Зарезервированные слова

Зарезервированное слово — это подобная идентификатору зарезервированная последовательность символов. Зарезервированное слово нельзя использовать в качестве идентификатора за исключением его использования с префиксом @.

зарезервированное_слово: одно из
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

В некоторых положениях грамматики особые идентификаторы имеют особое значение, но не являются зарезервированными словами. Например, в объявлении свойства идентификаторы «get» и «set» имеют особое значение (§10.7.2). Идентификатор, отличный от get или set, никогда не разрешается в этих положениях, поэтому такое использование не приводит к конфликту с использованием этих слов в качестве идентификаторов.

2.4.4 Литералы

Литерал — это представление значения в исходном коде.

литерал:
логический_литерал
целочисленный_литерал
действительный_литерал
символьный_литерал
строковый_литерал
литерал_null

2.4.4.1 Логические литералы

Существуют два значения логического литерала: true и false.

логический_литерал:
true
false

Логический_литерал имеет тип bool.

2.4.4.2 Целочисленные литералы

Целочисленные литералы используются для записи значений, имеющих тип int, uint, long и ulong. Целочисленные литералы имеют две возможных формы: десятичную и шестнадцатеричную.

целочисленный_литерал:
десятичный_целочисленный_литерал
шестнадцатеричный_целочисленный_литерал

десятичный_целочисленный_литерал:
десятичные_цифры суффикс_целочисленного_типанеоб

десятичные_цифры:
десятичная_цифра
десятичные_цифры десятичная_цифра

десятичная_цифра: одно из
0 1 2 3 4 5 6 7 8 9

суффикс_целочисленного_типа: одно из
U u L l UL Ul uL ul LU Lu lU lu

шестнадцатеричный_целочисленный_литерал:
0x 16_р_цифры суффикс_целочисленного_типанеоб
0X 16_р_цифры суффикс_целочисленного_типанеоб

16_р_цифры:
16_р_цифра
16_р_цифры 16_р_цифра

16_р_цифра: одно из
0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f

Тип целочисленного литерала определяется следующим образом:

· если литерал без суффикса, его тип — первый из этих типов, в которых его значение может быть представлено: int, uint, long и ulong;

· если литерал с суффиксом U или u, его тип — первый из этих типов, в которых его значение может быть представлено: uint и ulong;

· если литерал с суффиксом L или l, его тип — первый из этих типов, в которых его значение может быть представлено: long и ulong;

· если литерал с суффиксом UL, Ul, uL, ul, LU, Lu, lU или lu, его тип ulong.

Если представленное целочисленным литералом значение находится за пределами диапазона типа ulong, выдается ошибка времени компиляции.

Рекомендуется использовать «L» вместо «l» при записи литералов типа long, так как букву «l» легко спутать с цифрой «1».

Для записи минимально возможных значений int и long в виде десятичных целочисленных литералов существуют следующие два правила:

· если десятичный_целочисленный_литерал со значением 2147483648 (231) и без суффикса_целочисленного_типа появляется в качестве лексемы, непосредственно следующей за лексемой оператора унарного минуса (§7.6.2), результатом является константа типа int со значением −2147483648 (−231). Во всех других случаях такой десятичный_целочисленный_литерал имеет тип uint;

· если десятичный_целочисленный_литерал со значением 9223372036854775808 (263) и без суффикса_целочисленного_типа или с суффиксом_целочисленного_типа L или l появляется в качестве лексемы, непосредственно следующей за лексемой оператора унарного минуса (§7.6.2), результатом является константа типа long со значением −9223372036854775808 (−263). Во всех других случаях такой десятичный_целочисленный_литерал имеет тип ulong.

2.4.4.3 Действительные литералы

Действительные литералы используются для записи значений типов float, double и decimal.

действительный_литерал:
десятичные_цифры . десятичные_цифры порядок_числанеоб суффикс_действительного_типанеоб
. десятичные_цифры порядок_числанеоб суффикс_действительного_типанеоб
десятичные_цифры порядок_числа суффикс_действительного_типанеоб
десятичные_цифры суффикс_действительного_типа

порядок_числа:
e знакнеоб десятичные_цифры
E знакнеоб десятичные_цифры

знак: одно из
+ -

суффикс_действительного_типа: одно из
F f D d M m

Если суффикс_действительного_типа не указан, типом действительного литерала является double. Иначе суффикс действительного типа определяет тип действительного литерала следующим образом:

· действительный литерал с суффиксом F или f имеет тип float. Например, литералы 1f, 1.5f, 1e10f и 123.456F все имеют тип float;

· действительный литерал с суффиксом D или d имеет тип double. Например, литералы 1d, 1.5d, 1e10d и 123.456D все имеют тип double;

· действительный литерал с суффиксом M или m имеет тип decimal. Например, литералы 1m, 1.5m, 1e10m и 123.456M все имеют тип decimal. Этот литерал преобразуется в значение типа decimal, принимая его точное значение и при необходимости округляя до ближайшего могущего быть представленным значения с помощью банковского округления (§4.1.7). Любой масштаб, видимый в литерале, сохраняется, если только значение не округляется и не равно нулю (в этом последнем случае знак и масштаб будут равны 0). Следовательно, синтаксический разбор литерала 2.900m создаст десятичное значение со знаком 0, коэффициентом 2900 и масштабом 3.

Если указанный литерал не удается представить в предписанном типе, выдается ошибка времени компиляции.

Значение действительного литерала с типом float или double определяется с помощью режима «округления до ближайшего» по стандарту IEEE.

Обратите внимание, что в действительном литерале всегда требуются десятичные цифры после десятичной точки. Например, 1.3F — это действительный литерал, а 1.F — нет.

2.4.4.4 Символьные литералы

Символьный литерал представляет один символ и обычно состоит из символа в кавычках, например 'a'.

символьный_литерал:
' символ '

символ:
один_символ
простая_управляющая_последовательность
шестнадцатеричная_escape-последовательность
escape-последовательность_Юникода

один_символ:
любой символ, кроме ' (U+0027), \ (U+005C) и символа_новой_строки

простая_управляющая_последовательность: одно из
\' \" \\ \0 \a \b \f \n \r \t \v

шестнадцатеричная_escape-последовательность:
\x 16_р_циф 16_р_цифнеоб 16_р_цифнеоб 16_р_цифнеоб

Символ, следующий за обратной косой чертой (\) в символе, должен быть одним из следующих: ', ", \, 0, a, b, f, n, r, t, u, U, x, v. В противном случае происходит ошибка времени компиляции.

Шестнадцатеричная escape-последовательность представляет собой один символ Юникода со значением, образованным шестнадцатеричным числом, следующим за «\x».

Если значение, представленное символьным литералом, больше U+FFFF, вызывается ошибка времени компиляции.

Управляющая последовательность символов Юникода (§2.4) в символьном литерале должна быть в диапазоне от U+0000 до U+FFFF.

Простая управляющая последовательность представляет собой кодировку символа Юникода, как показано в следующей таблице.

 

Escape-последовательность Имя символа Кодировка Юникода
\' Одинарная кавычка 0x0027
\" Двойные кавычки 0x0022
\\ Обратная косая черта 0x005C
\0 Null 0x0000
\a Предупреждение 0x0007
\b Возврат 0x0008
\f Перевод страницы 0x000C
\n Новая строка 0x000A
\r Возврат каретки 0x000D
\t Горизонтальная табуляция 0x0009
\v Вертикальная табуляция 0x000B

 

Символьный_литерал имеет тип char.

1.1.1.1 Строковые литералы

В C# поддерживается две формы строковых литералов: правильные строковые литералы и буквальные строковые литералы.

Правильный строковый литерал состоит из нуля или более символов, заключенных в двойные кавычки, например "hello", и может включать как простые управляющие последовательности (например \t для символа табуляции), так и шестнадцатеричные escape-последовательности и escape-последовательности Юникода.

Буквальный строковый литерал состоит из символа @ с последующими символом двойных кавычек, нулем или более символов и закрывающим символом двойных кавычек. Простым примером является @"hello". В буквальном строковом литерале символы между разделителями интерпретируются буквально, единственным исключением является управляющая_последовательность_кавычки. В частности, простые управляющие последовательности, шестнадцатеричные escape-последовательности и escape-последовательности Юникода не обрабатываются в буквальных строковых литералах. Буквальный строковый литерал может занимать несколько строк.

строковый_литерал:
правильный_строковый_литерал
буквальный_строковый_литерал

правильный_строковый_литерал:
" символы_правильного_строкового_литераланеоб "

символы_правильного_строкового_литерала:
символ_правильного_строкового_литерала
символы_правильного_строкового_литерала символ_правильного_строкового_литерала

символ_правильного_строкового_литерала:
один_символ_правильного_строкового_литерала
простая_управляющая_последовательность
шестнадцатеричная_escape-последовательность
escape-последовательность_Юникода

один_символ_правильного_строкового_литерала:
любой символ, кроме " (U+0022), \ (U+005C) и символа_новой_строки

буквальный_строковый_литерал:
@" символы_буквального_строкового_литераланеоб "

символы_буквального_строкового_литерала:
символ_буквального_строкового_литерала
символы_буквального_строкового_литерала символ_буквального_строкового_литерала

символ_буквального_строкового_литерала:
один_символ_буквального_строкового_литерала
управляющая_последовательность_кавычки

один_символ_буквального_строкового_литерала:
любой символ, кроме "

управляющая_последовательность_кавычки:
""

Символ, следующий за символом обратной косой черты (\) в символе_правильного_строкового_литерала, должен быть одним из следующих символов: ', ", \, 0, a, b, f, n, r, t, u, U, x, v. В противном случае происходит ошибка времени компиляции.

В примере

string a = "hello, world"; // hello, world
string b = @"hello, world"; // hello, world

string c = "hello \t world"; // hello world
string d = @"hello \t world"; // hello \t world

string e = "Joe said \"Hello\" to me"; // Joe said "Hello" to me
string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" to me

string g = "\\\\server\\share\\file.txt"; // \\server\share\file.txt
string h = @"\\server\share\file.txt"; // \\server\share\file.txt

string i = "one\r\ntwo\r\nthree";
string j = @"one
two
three";

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

Поскольку в шестнадцатеричной escape-последовательности может быть переменное число шестнадцатеричных цифр, строковый литерал "\x123" содержит один символ с шестнадцатеричным значением 123. Чтобы создать строку, содержащую символ с шестнадцатеричным значением 12 и с последующим символом 3, можно написать "\x00123" или "\x12" + "3".

Строковый_литерал имеет тип string.

По каждому строковому литералу не обязательно создается новый экземпляр строки. Если два или более строковых литерала, эквивалентных согласно оператору равенства строк (§7.9.7), появляются в одной программе, эти строковые литералы ссылаются на один и тот же экземпляр строки. Например, программа

class Test
{
static void Main() {
object a = "hello";
object b = "hello";
System.Console.WriteLine(a == b);
}
}

выведет True, так как эти два литерала ссылаются на один и тот же экземпляр строки.

Null

литерал_null:
null

Литерал_null может быть неявно преобразован в ссылочный тип или обнуляемый тип.

2.4.5 Операторы и знаки пунктуации

Существует несколько видов операторов и знаков пунктуации. Операторы используются в выражениях для описания операций, включающих один или более операндов. Например, в выражении a + b используется оператор + для сложения двух операндов a и b. Знаки пунктуации служат для группирования и разделения.

оператор_или_знак_пунктуации: одно из
{ } [ ] ( ) . , : ;
+ - * / % & | ^ ! ~
= < > ? ?? :: ++ -- && ||
-> == != <= >= += -= *= /= %=
&= |= ^= << <<= =>

shift_cправа:
>|>

присваивание_shift_справа:
>|>=

Вертикальная черта в порождениях shift_cправа и присваивания_shift_справа указывает, что, в отличие от других порождений в синтаксической грамматике, никакие символы (даже пробелы) не допустимы между этими лексемами. Эти порождения обрабатываются особо, чтобы сделать возможной правильную обработку списков_параметров_типа (§10.1.3).

2.5 Препроцессорные директивы

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

ПРО_директива:
ПРО_объявление
ПРО_условное_выражение
ПРО_строка
ПРО_диагностика
ПРО_область
ПРО_pragma

Доступны следующие препроцессорные директивы:

· #define и #undef, используемые, соответственно, для определения и отмены определения символов условной компиляции (§2.5.3);

· #if, #elif, #else и #endif, используемые для условного пропуска разделов исходного кода (§2.5.4);

· #line, используемая для управления номерами строк, выдаваемыми для ошибок и предупреждений (§2.5.7);

· #error и #warning, используемые для выдачи, соответственно, ошибок и предупреждений (§2.5.5);

· #region и #endregion, используемые для явного выделения разделов исходного кода (§2.5.6);

· #pragma, используемая для задания компилятору необязательных сведений о контексте (§2.5.8).

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

Исходная строка, содержащая директиву #define, #undef, #if, #elif, #else, #endif или #line, может оканчиваться однострочным комментарием. Комментарии с разделителями (вид комментариев с /* */) не разрешены в исходных строках, содержащих препроцессорные директивы.

Препроцессорные директивы не являются лексемами и не являются частью синтаксической грамматики C#. Тем не менее, препроцессорные директивы можно использовать для включения или исключения последовательностей лексем и таким образом влиять на значение программы C#. Например, скомпилированная программа:

#define A
#undef B

class C
{
#if A
void F() {}
#else
void G() {}
#endif

#if B
void H() {}
#else
void I() {}
#endif
}

приведет к той же последовательности лексем, что и программа:

class C
{
void F() {}
void I() {}
}

Таким образом, несмотря на то, что лексически эти программы различаются, синтаксически они идентичны.

2.5.1 Символы условной компиляции

Функциональность условной компиляции, предоставляемая директивами #if, #elif, #else и #endif, управляется посредством препроцессорных выражений (§2.5.2) и символов условной компиляции.

условный_символ:
любой идентификатор_или_зарезервированное_слово, за исключением true и false

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

При ссылке в препроцессорном выражении определенный символ условной компиляции имеет логическое значение true, а неопределенный — логическое значение false. Символы условной компиляции не обязательно должны быть явно объявлены, чтобы на них можно было ссылаться в препроцессорных выражениях. Необъявленные символы просто являются неопределенными и имеют, таким образом, значение false.

Пространство имен для символов условной компиляции отличается и является отдельным от всех других именованных сущностей в программе на C#. На символы условной компиляции можно ссылаться только в директивах #define и #undef и в препроцессорных выражениях.

2.5.2 Препроцессорные выражения

Препроцессорные выражения могут быть в директивах #if и #elif. В препроцессорных выражениях разрешены операторы !, ==, !=, && и ||, для группирования можно использовать скобки.

ПРО_выражение:
пробелнеоб ПРО_выражение_ИЛИ пробелнеоб

ПРО_выражение_ИЛИ:
ПРО_выражение_И
ПРО_выражение_ИЛИ пробелнеоб || пробелнеоб ПРО_выражение_И

ПРО_выражение_И:
ПРО_выражение_равенства
ПРО_выражение_И пробелнеоб && пробелнеоб ПРО_выражение_равенства

ПРО_выражение_равенства:
ПРО_унарное_выражение
ПРО_выражение_равенства пробелнеоб == пробелнеоб ПРО_унарное_выражение
ПРО_выражение_равенства пробелнеоб != пробелнеоб ПРО_унарное_выражение

ПРО_унарное_выражение:
ПРО_первичное_выражение
! пробелнеоб ПРО_унарное_выражение

ПРО_первичное_выражение:
true
false
условный_символ
( пробелнеоб ПРО_выражение пробелнеоб )

При ссылке в препроцессорном выражении определенный символ условной компиляции имеет логическое значение true, а неопределенный — логическое значение false.

Вычисление препроцессорного выражения всегда дает логическое значение. Правила вычисления для препроцессорного выражения такие же, как для константного выражения (§7.18), с тем исключением, что единственные определенные пользователем сущности, на которые можно ссылаться, — это символы условной компиляции.

2.5.3 Директивы объявлений

Директивы объявлений используются для определения или отмены определения символов условной компиляции.

ПРО_объявление:
пробелнеоб # пробелнеоб define пробел условный_символ ПРО_новая_строка
пробелнеоб # пробелнеоб undef пробел условный_символ ПРО_новая_строка

ПРО_новая_строка:
пробелнеоб однострочный_комментарийнеоб новая_строка

Обработка директивы #define делает данный символ условной компиляции определенным, начиная с исходной строки, следующей за директивой. Подобным же образом обработка директивы #undef делает данный символ условной компиляции неопределенным, начиная с исходной строки, следующей за директивой.

Все директивы #define и #undef в исходном файле должны стоять до первой лексемы (§2.3.3) в исходном файле, иначе вызывается ошибка времени компиляции. Интуитивно понятно, что директивы #define и #undef должны предшествовать любому «фактическому коду» в исходном файле.

Следующий пример

#define Enterprise

#if Professional || Enterprise
#define Advanced
#endif

namespace Megacorp.Data
{
#if Advanced
class PivotTable {...}
#endif
}

правильный, так как директивы #define предшествуют первой лексеме (зарезервированное слово namespace) в исходном файле.

Следующий пример приводит к ошибке времени компиляции, так как директива #define следует за фактическим кодом:

#define A
namespace N
{
#define B
#if B
class Class1 {}
#endif
}

Директива #define может определять уже определенный символ условной компиляции, без какого-либо вмешательства директивы #undef для этого символа. В следующем примере символ условной компиляции A определен, а затем определен снова.

#define A
#define A

Директива #undef может «отменить определение» не определенного символа условной компиляции. В следующем примере определен символ условной компиляции A, а затем дважды отменено его определение; хотя вторая директива #undef не действует, она все же допустима.

#define A
#undef A
#undef A

2.5.4 Директивы условной компиляции

Директивы условной компиляции используются для условного включения или исключения частей исходного файла.

ПРО_условное_выражение:
ПРО_раздел_if ПРО_разделы_elifнеоб ПРО_раздел_elseнеоб ПРО_endif

ПРО_раздел_if:
пробелнеоб # пробелнеоб if пробел ПРО_выражение ПРО_новая_строка условный_разделнеоб

ПРО_разделы_elif:
ПРО_раздел_elif
ПРО_разделы_elif ПРО_раздел_elif

ПРО_раздел_elif:
пробелнеоб # пробелнеоб elif пробел ПРО_выражение ПРО_новая_строка условный_разделнеоб

ПРО_раздел_else:
пробелнеоб # пробелнеоб else ПРО_новая_строка условный_разделнеоб

ПРО_endif:
пробелнеоб # пробелнеоб endif ПРО_новая_строка

условный_раздел:
раздел_ввода
пропущенный_раздел

пропущенный_раздел:
часть_пропущенного_раздела
пропущенный_раздел часть_пропущенного_раздела

часть_пропущенного_раздела:
пропущенные_символынеоб новая_строка
ПРО_директива

пропущенные_символы:
пробелнеоб не_знак_числа входные_символынеоб

не_знак_числа:
любой входной_символ, кроме #

В соответствии с синтаксисом, директивы условной компиляции должны быть записаны как наборы, состоящие из, по порядку, директивы #if, нуля или более директив #elif, нуля или одной директивы #else и директивы #endif. Между этими директивами находятся условные разделы исходного кода. Каждый раздел управляется непосредственно предшествующей директивой. Условный раздел может содержать вложенные директивы условной компиляции при условии, что они образуют полные наборы.

ПРО_условное_выражение выделяет не более одного из содержащихся условных_разделов для обычной лексической обработки:

· ПРО_выражения директив #if и #elif вычисляются по порядку, пока одна из них не выдаст значение true. Если выражение выдает true, выбирается условный_раздел соответствующей директивы;

· если все ПРО_выражения дают false и если имеется директива #else, выбирается условный_раздел директивы #else;

· иначе условный_раздел не выбирается.

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

Оставшиеся условные_разделы, если они есть, обрабатываются как пропущенные_разделы: за исключением препроцессорных директив, исходный код в разделе не обязан соблюдать лексическую грамматику; в этом разделе не создаются лексемы из исходного кода, а препроцессорные директивы должны быть лексически правильными, но в других отношениях они не обрабатываются. Внутри условного_раздела, который обрабатывается как пропущенный_раздел, любые вложенные условные_разделы (содержащиеся во вложенных конструкциях #if...#endif и #region...#endregion) также обрабатываются как пропущенные_разделы.

В следующем примере показано, как могут вкладываться директивы условной компиляции:

#define Debug // отладка включена
#undef Trace // трассировка отключена

class PurchaseTransaction
{
void Commit() {
#if Debug
CheckConsistency();
#if Trace
WriteToLog(this.ToString());
#endif
#endif
CommitHelper();
}
}

За исключением препроцессорных директив, пропущенный исходный код не является предметом лексического анализа. Например, следующее допустимо, несмотря на незавершенный комментарий в разделе #else:

#define Debug // отладка включена

class PurchaseTransaction
{
void Commit() {
#if Debug
CheckConsistency();
#else
/* Выполнение других действий
#endif
}
}

При этом следует учитывать, что препроцессорные директивы должны быть лексически правильными даже в пропущенных разделах исходного кода.

Препроцессорные директивы не обрабатываются, если они находятся внутри элементов ввода в несколько строк. Например, программа:

class Hello
{
static void Main() {
System.Console.WriteLine(@"hello,
#if Debug
world
#else
Nebraska
#endif
");
}
}

имеет результатом:

hello,
#if Debug
world
#else
Nebraska
#endif

В особенных случаях обрабатываемый набор препроцессорных директив может зависеть от вычисления ПРО_выражения. В примере

#if X
/*
#else
/* */ class Q { }
#endif

в любом случае создается тот же самый поток лексем (class Q { }), независимо от того, определен X или нет. Если X определен, из-за многострочного комментария обрабатываются только директивы #if и #endif. Если X не определен, то три директивы (#if, #else и #endif) являются частью набора директив.

2.5.5 Директивы диагностики

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

ПРО_диагностика:
пробелнеоб # пробелнеоб error ПРО_сообщение
пробелнеоб # пробелнеоб warning ПРО_сообщение

ПРО_сообщение:
новая_строка
пробел входные_символынеоб новая_строка

В примере

#warning Code review needed before check-in

#if Debug && Retail
#error A build can't be both debug and retail
#endif

class Test {...}

в любом случае выдается предупреждение («Code review needed before check-in») и создается ошибка времени компиляции («A build can’t be both debug and retail»), если определены оба условных символа Debug и Retail. Обратите внимание, что ПРО_сообщение может содержать произвольный текст; в частности, оно не должно содержать правильно сформированные лексемы; об этом свидетельствует использование одиночной кавычки в слове can’t.

2.5.6 Директивы областей

Директивы областей используются для явного выделения областей исходного кода.

ПРО_область:
ПРО_начало_области условный_разделнеоб ПРО_конец_области

ПРО_начало_области:
пробелнеоб # пробелнеоб region ПРО_сообщение

ПРО_конец_области:
пробелнеоб # пробелнеоб endregion ПРО_сообщение

В области не вложены семантические значения; области используют программисты и автоматические средства, чтобы пометить раздел исходного кода. Сообщение, указанное в директиве #region или #endregion, также не имеет семантического значения, оно служит только для идентификации области. В соответствующих директивах #region и #endregion могут быть разные ПРО_сообщения.

Лексическая обработка области

#region
...
#endregion

точно соответствует лексической обработке директивы условной компиляции следующей формы:

#if true
...
#endif

2.5.7 Директивы строк

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

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

ПРО_строка:
пробелнеоб # пробелнеоб line пробел индикатор_строки ПРО_новая_строка

индикатор_строки:
десятичные_цифры пробел имя_файла
десятичные_цифры
default
hidden

имя_файла:
" символы_имени_файла "

символы_имени_файла:
символ_имени_файла
символы_имени_файла символ_имени_файла

символ_имени_файла:
любой символ_ввода, кроме "

При отсутствии директив #line компилятор сообщает в своем выводе настоящие номера строк и имена исходных файлов. При обработке директивы #line, содержащей индикатор_строки, не являющийся default, компилятор обрабатывает строку после этой директивы как строку с указанным номером (и именем файла, если оно указано).

Директива #line default отменяет действие всех предшествующих директив #line. Компилятор сообщает настоящие сведения о строках для последующих строк, как если бы директивы #line не обрабатывались.

Директива #line hidden не влияет на файл и номера строк, передаваемые в сообщениях об ошибках, но влияет на отладку на исходном уровне. При отладке все строки между директивой #line hidden и последующей директивой #line (не являющейся директивой #line hidden) не имеют сведений о номерах строк. При пошаговом выполнении кода в отладчике эти строки полностью пропускаются.

Обратите внимание, что имя_файла отличается от правильных строковых литералов тем, что escape-символы не обрабатываются; символ «\» просто означает обычный символ обратной косой черты в имени_файла.

Pragma

Препроцессорная директива #pragma используется для указания компилятору необязательных сведений о контексте. Сведения, предоставленные в директиве #pragma, ни в каком случае не изменяют семантику программы.

ПРО_pragma:
пробелнеоб # пробелнеоб pragma пробел тело_директивы_pragma ПРО_новая_строка

тело_директивы_pragma:
тело_предупреждения_директивы_pragma

C# предоставляет директивы #pragma для управления предупреждениями компилятора. В будущие версии языка могут быть включены дополнительные директивы #pragma. Чтобы обеспечить взаимодействие с другими компиляторами C#, компилятор C# корпорации Майкрософт не выдает ошибки компиляции для неизвестных директив #pragma; тем не менее, такие директивы создают предупреждения.

Pragma warning

… … …

3. Основные принципы

3.1 Запуск приложений.

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

Домен приложения обеспечивает изоляцию приложения путем функционирования в качестве контейнера состояния приложения. Домен приложения функционирует в качестве контейнера и границы типов, заданных в приложении, и используемых библиотек классов. Типы, загружаемые в один домен приложения, отличаются от аналогичных типов, загруженных в другой домен приложения, а экземпляры объектов не используются совместно непосредственным образом между доменами приложений. Например, каждый домен приложения имеет свою копию статических переменных для данных типов, а статический конструктор для типа выполняется не более одного раза на домен приложения. Можно обеспечивать характерные реализации политики или механизмы для создания или удаления доменов приложений.

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

static void Main() {...}

static void Main(string[] args) {...}

static int Main() {...}

static int Main(string[] args) {...}

Как указано выше, точка входа может дополнительно возвратить значение int. Данное возвращаемое значение используется в завершении приложения (§3.2).

Точка входа может дополнительно иметь один формальный параметр. Параметр может иметь любое имя, но тип параметра должен быть string[]. При наличии формального параметра среда выполнения создает и передает аргумент string[], содержащий аргументы командной строки, заданные при запуске приложения. Аргумент string[] никогда не принимает значение null, но может иметь нулевую длину, если не были заданы аргументы командной строки.

Так как C# поддерживает перегрузку методов, класс или структура могут содержать несколько определений какого-либо метода при условии того, что их сигнатуры отличаются. Однако в рамках одной программы класс или структуры не могут содержать более одного метода Main, определение которого позволяет использовать этот метод в качестве точки входа приложения. Другие перегруженные версии Main разрешены с условием наличия более одного параметра или с условием наличия только параметра, тип которого не является типом string[].

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

В C# каждый метод должен быть задан в качестве члена класса или структуры. Как правило, объявленная доступность (§3.5.1) метода определяется модификаторами доступа (§10.3.5), указанными в объявлении, и, аналогичным образом, объявленная доступность типа определяется модификаторами доступа, указанными в объявлении. Чтобы заданный метод заданного типа можно было вызвать, и тип, и член должны быть доступны. Однако точка входа приложения является особым случаем. В частности, среда выполнения может вызвать точку доступа приложения независимо от объявленной доступности в объявлениях вмещающих типов.

Метод точки входа приложения может не находиться в объявлении универсального класса.

Во всех других отношениях методы точки входа функционируют аналогично другим методам.

3.2 Завершение приложения.

Завершение приложения возвращает управление среде выполнения.

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

Если возвращаемое значение метода точки входа имеет тип void, то достижение правой фигурной скобки (}), завершающей данный метод, или выполнение инструкции return без выражения приведет к коду статуса завершения со значением 0.

Перед завершением приложения для всех его объектов, которые еще не были собраны сборщиком мусора, вызываются деструкторы, если такая очистка не была отключена (например, путем вызова метода библиотеки GC.SuppressFinalize).

3.3 Объявления.

Объявления в программе на C# определяют составные элементы программы. Программы на C# организованы посредством использования пространств имен (§9), которые могут содержать объявления типов и объявления вложенных пространств имен. Объявления типов (§9.6) используются для задания классов (§10), структур (§10.14), интерфейсов (§13), перечисляемых типов (§14) и делегатов (§15). Типы членов, разрешенных в объявлении типов, зависят от формы объявления типа. Например, объявления классов могут содержать объявления для констант (§10.4), полей (§10.5), методов (§10.6), свойств (§10.7), событий (§10.8), индексаторов (§10.9), операторов (§10.10), конструкторов экземпляров (§10.11), статических конструкторов (§10.12), деструкторов (§10.13) и вложенных типов (§10.3.8).

Объявление определяет имя в области объявления, которому принадлежит объявление. За исключением перегруженных членов (§3.6), наличие двух или более объявлений, представляющих члены с одним именем в области объявления, является ошибкой времени компиляции. Недопустимо, чтобы область объявления содержала различные типы членов с одинаковыми именами. Например, область объявления не может содержать поля и метода с одинаковыми именами.

Существует несколько типов областей объявлений, которые представлены далее.

· В рамках всех исходных файлов программы объявления_членов_пространства_имен без вмещающего объявления_пространства_имен являются членами единой комбинированной области объявления, которая называется глобальная область объявления.

· В рамках всех исходных файлов программы объявления_членов_пространства_имен в объявлении_пространства_имен с аналогичным полным именем пространства имен являются членами одной комбинированной области объявления.

· Каждый класс, структура или интерфейс объявления создает новую область объявления. Имена вносятся в данную область объявлений посредством объявлений_членов_класса, объявлений_членов_структур, объявлений_членов_интерфейса или параметров_типа. Исключая перегруженные объявления конструктора экземпляров и объявления статического конструктора, класс или структура не могут содержать объявление члена с одинаковым именем в классе или структуре. Класс, структура или интерфейс допускает объявление перегруженных методов и индексаторов. Более того, класс или структура допускают объявление перегруженных конструкторов экземпляров и операторов. Например, класс, структура или интерфейс могут содержать несколько объявлений методов с одним именем с условием, что данные объявления методов отличаются по сигнатуре (§3.6). Обратите внимание, что базовые классы не размещаются в области объявления класса, а также базовые интерфейсы не размещаются в области объявления интерфейса. Таким образом производный класс или интерфейс могут содержать объявление члена с одинаковым именем в качестве унаследованного члена. Такой член скрывает унаследованный член.

· Каждое объявление делегата создает новую область объявления. Имена размещаются в данной области объявления посредством параметров_типа.

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

· Каждый блок или блок ветвления, а также инструкции for, foreach и using создают область объявлений для локальных переменных и локальных констант, которая называется область объявлений локальных переменных. Имена размещаются в данной области объявлений посредством объявлений_локальных_переменных и объявлений_локальных_констант. Если блок является телом объявления конструктора экземпляра, метода или оператора, методом доступа get или set для объявления индексатора или анонимной функцией, параметры, заявленные в данном конструкторе, являются членами области объявлений локальных переменных блока . Аналогично любое выражение, возникающее в качестве тела анонимной функции в форме лямбда_выражения, создает область объявления, содержащую параметры анонимной функции. Одинаковое имя двух членов области объявлений локальных переменных является ошибкой. Наличие в области объявления локальных переменных блока и вложенной области объявления локальных переменных элементов с одинаковым именем является ошибкой. Таким образом, в рамках вложенной области объявления невозможно объявить локальную переменную или константу с одинаковым именем в родительской области объявления. Две области объявления могут содержать элементы с одинаковыми названиями, если ни одна из областей не содержит другую.

· Каждый блок или switch_блок создает отдельную область объявления для меток. Имена размещаются в данной области объявления посредством помеченных_операторов, а также ссылки на имена даются посредством операторов_goto. Область объявления метки блока включает в себя любые вложенные блоки. Таким образом в рамках вложенного блока невозможно объявить метку с одинаковым именем метки в родительском блоке.

Текстовый порядок, в котором имена объявляются, в общем случае не имеет значения. В частности, текстовый порядок не имеет значения для объявления и использования пространств имен, констант, методов, свойств, событий, индексаторов, операторов, конструкторов экземпляров, деструкторов, статических конструкторов и типов. Порядок объявления имеет значение в следующих случаях.

· Порядок объявления объявлений полей и локальных переменных определяет порядок выполнения их инициализаторов (при их наличии).

· Локальные переменные должны быть заданы до их использования (§3.7).

· Порядок объявлений членов перечислений (§14.3) важен, если опущены значения константных_выражений.

Область объявления пространства имен является «открыто завершенной», поэтому два объявления пространств имен с одинаковыми полными именами размещаются в одной области объявления. Например:

namespace Megacorp.Data
{
class Customer
{
...
}
}

namespace Megacorp.Data
{
class Order
{
...
}
}

Два рассмотренных выше объявления пространств имен размещаются в одной области объявления, и в этом случае объявляются два класса с полными именами Megacorp.Data.Customer и Megacorp.Data.Order. Поскольку два объявления размещаются в одной области объявления, если в каждом объявлении содержится объявление класса с одинаковым именем, происходит ошибка времени компиляции.

Согласно указанному выше, область объявления блока содержит любые вложенные блоки. Поэтому в следущем примере методы F и G приводят к ошибке времени выполнения, так как имя i объявлено во внешнем блоке и не может быть повторно объявлено во внутреннем блоке. Однако методы H и I верны, так как два имени i объявлены в отдельных невложенных блоках.

class A
{
void F() {
int i = 0;
if (true) {
int i = 1;
}
} void G() {
if (true) {
int i = 0;
}
int i = 1;
}

void H() {
if (true) {
int i = 0;
}
if (true) {
int i = 1;
}
}

void I() {
for (int i = 0; i < 10; i++)
H();
for (int i = 0; i < 10; i++)
H();
}
}

3.4 Члены.

Пространства имен и типы содержат члены. Члены сущности обычно доступны посредством использования проверенного имени, начинающегося со ссылки на сущность, затем следует маркер «.», а затем имя члена.

Члены типа либо объявляются в объявлении типа, либо наследуются из базового класса типа. Когда тип наследуется из базового класса, все члены базового класса, исключая конструкторы экземпляров, деструкторы и статические конструкторы, становятся членами производного типа. Объявленная доступность члена базового класса не контролирует наследование члена, наследование распространяется на каждый член, не являющийся конструктором экземпляров, статическим конструктором или деструктором. Однако наследованный член может быть недоступен в производном типе, либо по причине объявленной доступности (§3.5.1), либо по причине того, что он скрыт объявлением в самом типе (§3.7.1.2).

3.4.1 Члены пространства имен.

Пространства имен и типы, имеющие родительское пространство имен, являются членами глобального пространства имен. Это непосредственно соответствует именам, объявленным в глобальной области объявления.

Пространства имен и типы, объявленные в рамках пространства имен, являются членами данного пространства имен. Это непосредственно соответствует именам, объявленным в области объявления пространства имен.

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

3.4.2 Члены структуры.

Членами структуры являются члены, объявленные в структуре, и члены, унаследованные от непосредственного базового класса структуры System.ValueType и косвенного базового класса object.

Члены простого типа непосредственно соответствуют членам типа структуры с псевдонимами по простому типу.

· Члены sbyte являются членами структуры System.SByte.

· Члены byte являются членами структуры System.Byte.

· Члены short являются членами структуры System.Int16.

· Члены ushort являются членами структуры System.UInt16.

· Члены int являются членами структуры System.Int32.

· Члены uint являются членами структуры System.UInt32.

· Члены long являются членами структуры System.Int64.

· Члены ulong являются членами структуры System.UInt64.

· Члены char являются членами структуры System.Char.

· Члены float являются членами структуры System.Single.

· Члены double являются членами структуры System.Double.

· Члены decimal являются членами структуры System.Decimal.

· Члены bool являются членами структуры System.Boolean.

3.4.3 Члены перечисления.

Членами перечисления являются члены, объявленные в перечислении, и члены, унаследованные от непосредственного базового класса перечисления System.Enum и косвенных базовых классов System.ValueType и object.

3.4.4 Члены класса.

Членами класса являются члены, объявленные в классе, и члены, унаследованные от базового класса (исключая класс object, не имеющего базовый класс). Члены, унаследованные из базового класса, включают константы, поля, методы, свойства, события, индексаторы, операторы и типы базового класса, но не включают конструкторы экземпляров, деструкторы и статические конструкторы базового класса. Члены базового класса наследуются вне зависимости от их доступности.

Объявление класса может содержать объявления констант, полей, методов, свойств, событий, индексаторов, операторов, конструкторов экземпляров, деструкторов, статических конструкторов и типов.

Члены object и string непосредственно соответствуют членам типов класса, псевдонимами которых они являются.

· Члены object являются членами структуры System.Object.

· Члены string являются членами структуры System.String.

3.4.5 Члены интерфейса.

Членами интерфейса являются члены, объявленные в интерфейсе и во всех базовых интерфейсах данного интерфейса. Члены в классе object, собственно говоря, не являются членами какого-либо интерфейса (§13.2). Однако члены в классе object доступны посредством поиска членов в любом типе интерфейса (§7.3).

3.4.6 Члены массива.

Членами массива являются члены, унаследованные из класса System.Array.

3.4.7 Члены делегата.

Членами делегата являются члены, унаследованные из класса System.Delegate.

3.5 Доступ для членов.

Объявления членов позволяют осуществлять контроль доступа членов. Доступность члена устанавливается посредством объявленной доступности (§3.5.1) члена в сочетании с доступностью непосредственно содержащем типе, при его наличии.

Когда доступ к определенному члену разрешен, член является доступным. И наоборот, когда доступ к определенному члену запрещен, член является недоступным. Доступ к члену разрешен, когда текстовое положение, в котором происходит доступ, включено в домен доступности (§3.5.2) члена.

3.5.1 Объявленная доступность.

Возможные варианты объявленной доступности члена представлены ниже.

· Общая, выбираемая путем включения модификатора public в объявление члена. Интуитивное понимание модификатора public — «доступ не ограничен».

· Защищенная, выбираемая путем включения модификатора protected в объявление члена. Интуитивное понимание модификатора protected — «доступ ограничен до класс-контейнера или типов, производных от класс-контейнера».

· Внутренняя, выбираемая путем включения модификатора internal в объявление члена. Интуитивное понимание модификатора internal — «доступ ограничен до данной программы».

· Внутренняя защищенная (обозначает защищенную или внутреннюю доступность), выбираемая путем включения модификаторов protected и internal в объявление члена. Интуитивное понимание модификатор protected internal — «доступ ограничен до данной программы или типов, производных от класс-контейнера».

· Частная, выбираемая путем включения модификатора private в объявление члена. Интуитивное понимание модификатора private — «доступ ограничен до тип-контейнера».

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

· Пространства имен неявно имеют объявленную доступность public. Модификаторы доступа запрещены для объявлений пространств имен.

· Типы, объявленные в блоках компиляции, или пространства имен могут иметь объявленную доступность public или internal, а по умолчанию используется объявленная доступность internal.

· Члены класса могут иметь любую из пяти типов объявленной доступности, а по умолчанию используется объявленная доступность private. (Обратите внимание, что тип, объявленный в качестве члена класса, может иметь любую из пяти объявленных доступностей, тогда как тип, объявленный в качестве члена пространства имен, может иметь только объявленную доступность public или internal.)

· Члены структуры могут иметь объявленную доступность public, internal или private, а по умолчанию использует объявленная доступность private, так как структуры неявно запечатаны. Члены структуры, размещенные в структуре (т. е. не унаследованные данной структурой), не могут иметь статусы объявленной доступности protected или protected internal. (Обратите внимание, что тип, объявленный в качестве члена структуры, может иметь объявленную доступностей public, internal или private, тогда как тип, объявленный в качестве члена пространства имен, может иметь только объявленную доступность public или internal.)

· Члены интерфейса неявно имеют объявленную доступность public. Модификаторы доступа запрещены для объявлений члена интерфейса.

· Члены перечисления неявно имеют объявленную доступность public. Модификаторы доступа запрещены для объявлений члена перечисления.

3.5.2 Домены доступности.

Домен доступности члена состоит из (возможно разобщенно) раздела текста программы, в котором разрешен доступ к члену. С целью определения домена доступности члена он считается верхнего уровня, если он не объявлен в рамках типа, и считается вложенным, если он объявлен в рамках другого типа. Дополнительно определяется текст программы программы как весь текст программы, содержащийся во всех исходных файлах программы, а текст программы типа определяется как весь текст программы, содержащийся между открывающими и закрывающими маркерами «{" и "}» в теле_класса, теле_структуры, теле_интерфейса или теле_перечисления типа (включая, возможно, типы, вложенные в рамках типа).

Домен доступности предопределенного типа (например, object, int или double) неограничен.

Домен доступности свободного типа верхнего уровня T (§4.4.3), объявленного в программе P, определяется следующим образом.

· Если для T объявлена доступность public, доменом доступности T является текст программы P и любая программа, ссылающаяся на P.

· Если для T объявлена доступность internal, доменом доступности T является текст программы P.

Исходя из данных определений, домен доступности свободного типа верхнего уровня всегда по крайней мере является текстом программы программы, в которой тип объявлен.

Доменом доступности для сформированного типа T<A1, ...,AN> является пересечение домена доступности обобщенного свободного типа T и доменов доступности аргументов типа A1, ...,AN.

Домен доступности вложенного члена M, объявленного в типе T в программе P, определяется следующим образом (учитывая, что M может являться типом).

· Если для M объявлена доступность public, домен доступности M совпадает с доменом доступности T.

· Если для M объявлена доступность protected internal, пусть D будет объединением текста программы P и текста программы любого типа, произведенного из T, объявленного вне P. Доменом доступности M является пересечение домена доступности T с доменом доступности D.

· Если для M объявлена доступность protected, пусть D будет объединением текста программы T и текста программы любого типа, произведенного из T. Доменом доступности M является пересечение домена доступности T с доменом доступности D.

· Если для M объявлена доступность internal, доменом доступности M является пересечение домена доступности T с текстом программы P.

· Если для M объявлена доступность private, домен доступности M совпадает с текстом программы T.

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

Интуитивно это значит, что при доступе к типу или члену M для обеспечения разрешения доступа вычисляются следующие шаги.

· Сначала, если M объявлен в типе (в противоположность блоку компиляции или пространству имен), при недоступности данного типа возникает ошибка времени компиляции.

· Затем, если M объявлен как public, доступ разрешается.

· В противном случае, если M объявлен как protected internal, доступ разрешается в том случае, когда он происходит в рамках программы, в которой объявлен M, или когда он происходит в рамках класса, произведенного из класса, в котором объявлен M, и доступ выполняется посредством производного типа класса (§3.5.3).

· В противном случае, если M объявлен как protected, доступ разрешается в том случае, когда он происходит в рамках класса, в котором объявлен M, или когда он возникает в рамках класса, произведенного из класса, в котором объявлен M, и доступ выполняется посредством производного типа класса (§3.5.3).

· В противном случае, если M объявлен как internal, доступ разрешается, если он происходит в рамках программы, в которой объявлен M.

· В противном случае, если M объявлен как private, доступ разрешается, если он происходит в рамках типа, в котором объявлен M.

· В противном случае тип или член недоступны, и возникает ошибка времени компиляции.

В примере

public class A
{
public static int X;
internal static int Y;
private static int Z;
}

internal class B
{
public static int X;
internal static int Y;
private static int Z;

public class C
{
public static int X;
internal static int Y;
private static int Z;
}

private class D
{
public static int X;
internal static int Y;
private static int Z;
}
}

классы и члены имеют следующие области доступности.

· Домен доступности A и A.X неограничен.

· Доменом доступности A.Y, B, B.X, B.Y, B.C, B.C.X и B.C.Y является текст программы содержащей программы.

· Доменом доступности A.Z является текст программы A.

· Доменом доступности B.Z и B.D является текст программы B, включая текст программы B.C и B.D.

· Доменом доступности B.C.Z является текст программы B.C.

· Доменом доступности B.D.X и B.D.Y является текст программы B, включая текст программы B.C и B.D.

· Доменом доступности B.D.Z является текст программы B.D.

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

Согласно описанию в разделе §3.4 все члены базового класса, кроме конструкторов экземпляров, деструкторов и статических конструкторов, наследуются производными типами. Это касается даже закрытых членов базового класса. Однако домен доступности закрытого класса включает только текст программы типа, в котором член объявлен. В примере

class A
{
int x;

static void F(B b) {
b.x = 1; // Ok
}
}

class B: A
{
static void F(B b) {
b.x = 1; // Ошибка, x недоступен
}
}

класс B наследует закрытый член x из класса A. Так как член является закрытым, он доступен только в рамках тела_класса A. Поэтому доступ к b.x успешно осуществляется в методе A.F, но невозможен в методе B.F.

3.5.3 Защищенный доступ для членов экземпляров.

При доступе к члену экземпляра protected вне текста программы, в котором он объявлен, и при доступе к члену экземпляра protected internal вне текста программы, в котором он объявлен, доступ должен осуществляться в объявлении класса, образованным от класса, в котором он объявлен. Дополнительно доступ должен осуществляться через экземпляр производного типа класса или типа класса сконструированного из него. Данное ограничение предотвращает доступ одного производного класса к закрытым членам другого производного класса, даже если члены унаследованы от одного базового класса.

Пусть B будет классом, в котором объявлен защищенный член экземпляра M, и пусть D будет производным классом B. В рамках тела_класса D доступ к M может принять одну из следующих форм.

· Имя_типа или первичное_выражение без уточнения формы M.

· Первичное_выражение формы E.M с условием, что тип E является T или производным от T классом, где T является типом класса D или типом класса, сконструированным из D.

· Первичное_выражение формы base.M.

Дополнительно к данным формам доступа производный класс может осуществлять доступ к конструктору защищенных экземпляров базового класса в инициализаторе_конструктора (§10.11.1).

В примере

public class A
{
protected int x;

static void F(A a, B b) {
a.x = 1; // Ok
b.x = 1; // Ok
}
}

public class B: A
{
static void F(A a, B b) {
a.x = 1; // Ошибка, доступ должен осуществляться через экземпляр B
b.x = 1; // Ok
}
}

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

В примере

class C<T>
{
protected T x;
}

class D<T>: C<T>
{
static void F() {
D<T> dt = new D<T>();
D<int> di = new D<int>();
D<string> ds = new D<string>();
dt.x = default(T);
di.x = 123;
ds.x = "test";
}
}

три присваивания x разрешены, так как они все осуществляются через экземпляры типов класса, сконструированные из универсального типа.

3.5.4 Ограничения доступности.

Некоторым конструкциям языка C# требуется, чтобы тип был хотя бы доступен как член или другой тип. Считается, что доступность типа T не меньше доступности члена или типа M, если домен доступности T является множеством домена доступности M. Другими словами, доступность T не меньше доступности M, если T доступен во всех контекстах, в которых доступен M.

Существуют следующие ограничения доступности.

· Прямой базовый класс типа класса должен быть не менее доступен, чем сам тип класса.

· Явный базовый интерфейс типа интерфейса должен быть не менее доступен, чем сам тип интерфейса.

· Тип возвращаемого значения и типы параметров типа делегата должны быть не менее доступны, чем сам тип делегата.

· Тип константы должен быть не менее доступен, чем сама константа.

· Тип поля должен быть не менее доступен, чем само поле.

· Тип возвращаемого значения и типы параметров метода должны быть не менее доступны, чем сам метод.

· Тип свойства должен быть не менее доступен, чем само свойство.

· Тип события должен быть не менее доступен, чем само событие.

· Тип и типы параметра индексатора должны быть не менее доступны, чем сам индексатор.

· Тип возвращаемого значения и типы параметра оператора должны быть не менее доступны, чем сам оператор.

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

В примере

class A {...}

public class B: A {...}

класс B приводит к ошибке времени компиляции, так как A менее доступен, чем B.

Аналогично, в примере

class A {...}

public class B
{
A F() {...}

internal A G() {...}

public A H() {...}
}

метод H в B приводит к ошибке времени компиляции, так как тип возвращаемого значения A менее доступен, чем метод.

3.6 Сигнатуры и перегрузка.

Методы, конструкторы экземпляров, индексаторы и операторы характеризуются их сигнатурами.

· Сигнатура метода состоит из имени метода, числа параметров типа и типа и вида (значение, ссылка или выход) каждого ее параметра, учтенного в порядке слева направо. Для данных целей каждый параметр типа метода, возникающий в типе формального параметра, идентифицируется не по имени, а по порядковому номеру в списке аргументов типа метода. Сигнатура метода специально не содержит ни тип возвращаемого значения, ни модификатор params, который может быть указан для самого правого параметра, и не содержит дополнительных ограничений параметра типа.

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

· Сигнатура индексатора состоит из типа каждого его формального параметра, учтенного в порядке слева направо. Сигнатура индексатора специально не содержит тип элемента и не содержит модификатор params, который может быть указан для самого правого параметра.

· Сигнатура оператора состоит из имени оператора и типа каждого его формального параметра, учтенного в порядке слева направо. Сигнатура оператора специально не содержит тип результата.

Сигнатуры обеспечивают использование механизма перегрузки членов в классах, структурах и интерфейсах.

· Перегрузка методов позволяет классу, структуре или интерфейсу объявить несколько одноименных методов при условии, что их сигнатуры уникальны в рамках класса, структуры или интерфейса.

· Перегрузка конструкторов экземпляров позволяет классу или структуре объявить несколько конструкторов экземпляров при условии, что их сигнатуры уникальны в рамках класса или структуры.

· Перегрузка индексаторов позволяет классу, структуре или интерфейсу объявить несколько одноименных индексаторов при условии, что их сигнатуры уникальны в рамках класса, структуры или интерфейса.

· Перегрузка операторов позволяет классу или структуре объявить несколько одноименных операторов при условии, что их сигнатуры уникальны в рамках класса или структуры.

Несмотря на то, что модификаторы параметров out и ref считаются частью сигнатуры, члены, объявленные в одном типе, не могут отличаться по сигнатуре только параметрами ref и out. Если два члена, объявленные в одном типе, будут иметь одинаковые сигнатуры, в которых все параметры в обоих методах с модификаторами out изменены на модификаторы ref, возникает ошибка времени компиляции. Для других целей сопоставления сигнатур (например, для скрытия или переопределения) ref и out учитываются в качестве части сигнатуры и не соответствуют друг другу. (Данное ограничение позволяет простым способом транслировать программы на C# для выполнения в Common Language Infrastructure (CLI), не обеспечивающей способ определения методов, отличающихся исключительно по ref и out.)

В следующем примере представлен набор объявлений перегруженных методов с их сигнатурами.

interface ITest
{
void F(); // F()

void F(int x); // F(int)

void F(ref int x); // F(ref int)

void F(out int x); // F(out int) ошибка

void F(int x, int y); // F(int, int)

int F(string s); // F(string)

int F(int x); // F(int) ошибка

void F(string[] a); // F(string[])

void F(params string[] a); // F(string[]) ошибка
}

Обратите внимание, что модификаторы параметров ref и out (§10.6.1) являются частью сигнатуры. Поэтому F(int) и F(ref int) являются уникальными сигнатурами. Однако F(ref int) и F(out int) не могут быть объявлены в одном интерфейсе, так как их сигнатуры отличаются только модификаторами ref и out. Также обратите внимание, что тип возвращаемого значения и модификатор params не являются частью сигнатуры, поэтому невозможно выполнить перегрузку только на основе типа возвращаемого значения или на основе включения или исключения модификатора params. По существу, объявления методов F(int) и F(params string[]), идентифицированных выше, приводят к ошибке времени компиляции.

3.7 Области.

Областью является имя региона текста программы, в рамках которого можно ссылаться на сущность, объявленную именем без квалификации имени. Области могут быть вложенные, а внутренняя область может повторно объявлять значение имени из внешней области (однако это не снимает ограничение, наложенное §3.3, что в рамках вложенного блока невозможно объявить локальную переменную с именем, совпадающим с именем локальной переменной во внешнем блоке). Имя из внешней области при этом считается скрытым в регионе текста программы, покрываемом внутренней областью, а доступ к внешнему имени возможен только по квалифицирующему имени.

· Областью члена пространства имен, объявленного объявлением_члена_пространства_имен (§9.5), без вмещающего объявления_пространства_имен является полный текст программы.

· Областью члена пространства имен, объявленного объявлением_члена_пространства_имен в рамках объявления_пространства_имен с полным именем N, является тело_пространства_имен каждого объявления_пространства_имен с полным именем N, или начинающимся с N и следующим периодом.

· Область имени, определенного директивой_внешнего_псевдонима , распространяется на использование_директив, глобальные_атрибуты и объявления_членов_пространства_имен незамедлительно содержащегося блока компиляции или тела пространства имен. Директива_внешнего_псевдонима не вносит какие-либо новые члены в нижестоящую область объявления. Другими словами, директива_внешнего_псевдонима не является транзитивной, а скорее влияет только на блок компиляции или тело пространства имен, в котором она происходит.

· Область имени, заданная или импортированная путем использования_директивы (§9.4), распространяется на объявления_членов_пространства_имен_блока_компиляции или тела_пространства_имен, в котором содержится использование_директивы. Использование_директивы может привести к образованию нуля или более пространств имен или типов имен, доступных в рамках определенного блока_компиляции или тела_пространства_имен, но не может привести к созданию новых членов в нижестоящей области объявления. Другими словами, использование_директивы не является транзитивным, а скорее влияет только на блок_компиляции или тело_пространства_имен, в котором находится.

· Областью параметра типа, объявленного списком_параметров_типа в объявлении_класса (§10.1), является база_класса, предложения_ограничений_параметров_типа и тело_класса данного объявления_класса.

· Областью параметра типа, объявленного списком_параметров_типа в объявлении_структуры (§11.1), является интерфейс_структуры, предложения_ограничений_параметров_типа и тело_структуры данного объявления_структуры.

· Областью параметра типа, объявленного списком_параметров_типа в объявлении_интерфейса (§13.1), является база_интерфейса, предложения_ограничений_параметров_типа и тело_интерфейса данного объявления_интерфейса.

· Областью параметра типа, объявленного списком_параметров_типа в объявлении_делегата (§15.1), является тип_возвращаемого_значения, список_формальных_параметров и предложения_ограничений_параметров_типа данного объявления_делегата.

· Областью члена, объявленного объявлением_члена_класса (§10.1.6), является тело_класса, в котором сделано объявление. Кроме того, область члена класса распространяется на тела_класса производных классов, включенных в домен доступности (§3.5.2) члена.

· Областью члена, объявленного объявлением_члена_структуры (§11.2), является тело_структуры, в котором сделано объявление.

· Областью члена, объявленного объявлением_члена_перечисляемого_типа (§14.3), является тело_перечисляемого_типа, в котором сделано объявление.

· Областью параметра, объявленного в объявлении_метода (§10.6), является тело_метода данного объявления_метода.

· Областью параметра, объявленного в объявлении_индексатора (§10.9), являются объявления_метода_доступа данного объявления_индексатора.

· Областью параметра, объявленного в объявлении_оператора (§10.10), является блок данного объявления_оператора.

· Областью параметра, объявленного в объявлении_конструктора (§10.11), является инициализатор_конструктора и блок данного объявления_конструктора.

· Областью параметра, объявленного в ламбда_выражении (§), является тело_ламбда_выражения данного ламбда_выражения.

· Областью параметра, объявленного в выражении_анонимного_метода (§), является блок данного выражения_анонимного_метода.

· Областью метки, объявленной в помеченном_операторе (§8.4), является блок, в котором содержится объявление.

· Областью локальной переменной, объявленной в объявлении_локальной_переменной (§8.5.1), является блок, в котором содержится объявление.

· Областью локальной переменной, объявленной в блоке_ветвления оператора switch (§8.7.2), является блок_ветвления.

· Областью локальной переменной, объявленной в инициализаторе_for оператора for (§8.8.3), является инициализатор_for, условие_for, итератор_for и содержащий оператор оператора for.

· Областью локальной константы, объявленной в объявлении_локальной_константы (§8.5.2), является блок, в котором содержится объявление. Ссылка на локальную константу в текстовой позиции, предшествующей декларатору_константы, является ошибкой времени компилирования.

· Область переменной, объявленной в качестве части оператора_foreach, оператора_using, оператора_lock или выражения_запроса, определяется путем расширения заданной конструкции.

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

class A
{
void F() {
i = 1;
}

int i = 0;
}

В данном примере ссылка F на i до объявления является верной.

В рамках области локальной переменной ссылка на локальную переменную в текстовой позиции, предшествующей декларатору_локальной_переменной локальной переменной, будет являться ошибкой времени компилирования. Например:

class A
{
int i = 0;

void F() {
i = 1; // Ошибка, использование перед объявлением
int i;
i = 2;
}

void G() {
int j = (j = 1); // Действительно
}

void H() {
int a = 1, b = ++a; // Действительно
}
}

В вышеуказанном методе F первое присваивание i явно не ссылается на поле, объявленное во внешней области. Однако существует ссылка на локальную переменную, что приводит к ошибке времени компилирования, так как текстуальность предшествует объявлению переменной. В методе G использование j в инициализаторе для объявления j является верным, так как использование не предшествует декларатору_локальной_переменной. В методе H последующий декларатор_локальной_переменной верно ссылается на локальную переменную, объявленную в более раннем деклараторе_локальной_переменной в рамках того же объявления_локальной_переменной.

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

Значение имени в рамках блока может отличаться в зависимости от контекста, в котором используется имя. В примере

using System;

class A {}

class Test
{
static void Main() {
string A = "hello, world";
string s = A; // контекст выражения

Type t = typeof(A); // контекст типа

Console.WriteLine(s); // выводит строку "hello, world"
Console.WriteLine(t); // выводит "A"
}
}

имя A используется в контексте выражения для ссылки на локальную переменную A и в контексте типа для ссылки на класс A.

3.7.1 Скрытие имени.

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

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

3.7.1.1 Скрытие через вложение.

Скрытие имени через вложение возникает в результате вложения пространств имен или типов в рамках пространств имен, в результате вложения типов в рамках классов или структур и в результате объявлений параметров и локальных переменных.

В примере

class A
{
int i = 0;

void F() {
int i = 1;
}

void G() {
i = 1;
}
}

в рамках метода F переменный экземпляр i скрыт локальной переменной i, но в рамках метода G экземпляр i все еще ссылается на переменный экземпляр.

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

class Outer
{
static void F(int i) {}

static void F(string s) {}

class Inner
{
void G() {
F(1); // Вызывает Outer.Inner.F
F("Hello"); // Ошибка.
}

static void F(long l) {}
}
}

вызов F(1) приводит к вызову F, объявленному в Inner, так как все внешние вхождения F скрыты внутренним объявлением. По той же причине вызов F("Hello") приводит к ошибке времени компилирования.

3.7.1.2 Скрытие через наследование.

Скрытие имени через наследование происходит, когда классы или структуры повторно объявляют имена, унаследованные из базовых классов. Этот тип скрытия имени принимает одну из следующих форм.

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

· Метод, представленный в классе или структуре, скрывает все члены базового класса, не являющиеся методами, и все методы базового класса с такой же сигнатурой (имя метода и попадания, модификаторы и типы параметра).

· Индексатор, представленный в классе или структуре, скрывает все индексаторы базового класса с такой же сигнатурой (попадания и типы параметра).

Правила, управляющие объявлениями операторов (§10.10), делают невозможным для производного класса объявление оператора с сигнатурой, аналогичной сигнатуре оператора базового класса. Поэтому операторы никогда не скрывают друг друга.

В противоположность скрытию имени из внешней области, скрытие доступного имени из унаследованной области приводит к предупреждению. В примере

class Base
{
public void F() {}
}

class Derived: Base
{
public void F() {} // Предупреждение, скрытие унаследованного имени
}

объявление F в Derived приводит к предупреждению. Скрытие наследуемого имени не является ошибкой, поскольку это препятствует отдельному развитию базовых классов. Например, вышеуказанная ситуация могла наступить, так как более поздняя версия Base представила метод F, который не был представлен в более ранней версии класса. Если бы вышеупомянутая ситуация была ошибочной, любое изменение, сделанное для базового класса в библиотеке класса отдельной версии, могло потенциально привести к недопустимости производных классов.

Предупреждение, вызванное скрытием унаследованного имени, можно устранить с помощью модификатора new:

class Base
{
public void F() {}
}

class Derived: Base
{
new public void F() {}
}

Модификатор new указывает, что F в Derived является «новым», и что действительно требуется скрыть унаследованный член.

Объявление нового члена приводит к скрытию унаследованного члена только в рамках области нового члена.

class Base
{
public static void F() {}
}

class Derived: Base
{
new private static void F() {} // Скрытие Base.F только в Derived
}

class MoreDerived: Derived
{
static void G() { F(); } // Вызов Base.F
}

В вышеуказанном примере объявление F в Derived приводит к скрытию F, унаследованного от Base, но, так как новый F в Derived имеет частный доступ, его область не распространяется на MoreDerived. Поэтому вызов F() в MoreDerived.G действителен, и он приведет к вызову Base.F.

3.8 Имена пространств имен и типов.

Некоторые контексты в программе на C# требуют указания имени_пространства_имен или имени типа.

имя_пространства_имен:
имя_пространства_имен_или_типа

имя_типа:
имя_пространства_имен_или_типа

имя_пространства_имен или типа:
идентификатор_спискаopt_аргументов_типа
имя_пространства_имен_или_типа . идентификатор_спискаop_ аргументов_типа
член_квалифицированного_псевдонима

Имя_пространства_имен — это имя_пространства_имен_или_типа, ссылающееся на пространства имен. Согласно описанию ниже следующее разрешение имени_пространства_имен_или_типа от имени_пространства_имен должно ссылаться на пространства имен, в противном случае возникает ошибка времени компилирования. Аргументы типов (§4.4.1) не могут быть представлены в имени_пространства_имен (только типы с аргументами типа).

Имя_типа — это имя_пространства_имен_или_типа, ссылающееся на тип. Согласно описанию ниже следующее разрешение имени_пространства_имен_или_типа от имени_типа должно ссылаться на тип, в противном случае возникает ошибка времени компилирования.

Если имя_пространства_имен_или_типа является членом квалифицированного псевдонима, его значение соответствует описанию в разделе §9.7. В противном случае имя_пространства_имен_или_типа имеет одну из следующих четырех форм.

· I

· I<A1, ..., AK>

· N.I

· N.I<A1, ..., AK>

где I — отдельный идентификатор, N — имя_пространства_имени_или_типа и <A1, ..., AK> — дополнительный список_аргументов_типа. Если список_аргументов_типа не указан, K считается равным нулю.

Значение имени_пространства_имен_или_типа определяется следующим образом.

· Имя_пространства_имен_или_типа имеет одну из форм I или форму I<A1, ..., AK>.

o Если K имеет нулевое значение, имя_пространства_имен_или_типа отображается в теле универсального метода объявления (§10.6), а также если объявление включает параметр типа (§10.1.3) с именем I, имя_пространства_имен_или_типа ссылается на данный параметр типа.

o В противном случае, если имя_пространства_имен_или_типа отображается в рамках тела объявления типа, то для каждого типа экземпляра T (§10.3.1), начиная с типа экземпляра данного объявления типа, заканчивая типом экземпляра каждого включающего класса или объявления структуры (при наличии):

· Если K имеет нулевое значение, а объявление T включает параметр типа с именем I, то имя_пространства_имен_или_типа ссылается на данный параметр типа.

· В противном случае, если T или любой из его базовых типов содержит вложенный доступный тип с именем I и параметрами типа K, то имя_пространства_имен_или_типа ссылается на данный тип, сконструированный с заданными аргументами типа. При наличии более одного типа структуры выбирается тип, объявленный в рамках большего производного типа. Обратите внимание, что члены, не являющиеся типами (константы, поля, методы, свойства, индексаторы, операторы, экземпляры, конструкторы, деструкторы и статические конструкторы), и члены типа с различным числом параметров типа игнорируются при определении значения имени_пространства_имен_или_типа.

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

· K имеет нулевое значение, а I является именем пространства имен N.

o Если местоположение, содержащее имя_пространства_имен_или_типа, заключено объявлением пространства имен для N, а объявление пространства имен содержит директиву_внешнего_псевдонима или директиву_использования_псевдонима, связывающие имя I с пространством имен или типом, то имя_пространства_имен_или_типа неоднозначно, поэтому возникает ошибка времени компилирования.

o В противном случае имя_пространства_имен_или_типа ссылается на пространство имен с именем I в N.

· Наоборот, N содержит доступный тип с именем I и параметрами типа K.

o Если K имеет нулевое значение и местоположение, содержащее имя_пространства_имен_или_типа, заключено объявлением пространства имен для N, а объявление пространства имен содержит директиву_внешнего_псевдонима или директиву_использования_псевдонима, связывающие имя I с пространством имен или типом, то имя_пространства_имен_или_типа неоднозначно, поэтому возникает ошибка времени компилирования.

o В противном случае, имя_пространства_имен_или_типа ссылается на тип, сконструированный с заданными аргументами типа.

· Наоборот, местоположение, содержащее имя_пространства_имен_или_типа, заключено объявлением пространства имен для N.

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

o В противном случае, если пространства имен, импортированные путем директив_использования_пространств_имен, объявления пространства имен содержат точно один тип с именем I и параметрами типа K, то имя_пространства_имен_или_типа ссылается на данный тип, сконструированный с заданными аргументами типа.

o В противном случае, если пространства имен, импортированные путем директив_использования_пространств_имен, объявления пространства имен содержат более одного типа с именем I и параметрами типа K, то имя_пространства_имен_или_типа неоднозначно, поэтому возникает ошибка времени компилирования.

o В противном случае имя_пространства_имен_или_типа неопределено, поэтому возникает ошибка времени компилирования.

· Наоборот, Имя_пространства_имен_или_типа имеет одну из форм N.I или форму N.I<A1, ..., AK>. N впервые разрешается в качестве имени_пространства_имен_или_типа. Если разрешение N неуспешно, возникает ошибка времени компилирования. В противном случае N.I или N.I<A1, ..., AK> разрешаются следующим образом.

o Если K имеет нулевое значение и N ссылается на пространство имен и содержит вложенное пространство имен с именем I, то имя_пространства_имен_или_типа ссылается на данное вложенное пространство имен.

o В противном случае, если N ссылается на пространство имен и содержит доступный тип с именем I и параметрами типа K, то имя_пространства_имен_или_типа ссылается на данный тип, сконструированный с заданными аргументами типа.

o В противном случае, если N ссылается на тип класса (возможно, сконструированного) или структуры и содержит вложенный доступный тип с именем I и параметрами типа K, то имя_пространства_имен_или_типа ссылается на данный тип, сконструированный с заданными аргументами типа. При наличии более одного типа структуры выбирается тип, объявленный в рамках большего производного типа.

o В противном случае N.I является недействительным именем_пространства_имен_или_типа, поэтому возникает ошибка времени компилирования.

Ссылка имени_пространства_имен_или_типа на статический класс (§10.1.1.3) допускается только в следующих случаях.

· Имя_пространства_имен_или_типа T в имени_пространства_имен_или_типа формы T.I или

· Имя_пространства_имен_или_типа T в выражении_typeof (§7.5.11) формы typeof(T).

3.8.1 Полные имена.

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

· Если N является членом глобального пространства имен, его полное имя N.

· В противном случае его полное имя S.N, где S — полное имя пространства имен или типа, в котором N объявлен.

Другими словами, полное имя N представляет полный иерархический путь идентификаторов, ведущих к N, начиная с глобального пространства имен. Так как каждый член пространства имен или типа должен иметь уникальное имя, полное имя пространства имен или типа всегда уникально.

В следующем примере представлено несколько объявлений пространств имен и типов вместе с соответствующими полными именами.

class A {} // A

namespace X // X
{
class B // X.B
{
class C {} // X.B.C
}

namespace Y // X.Y
{
class D {} // X.Y.D
}
}

namespace X.Y // X.Y
{
class E {} // X.Y.E
}

3.9 Автоматическое управление памятью.

C# развертывает автоматическое управление памятью, что освобождает разработчиков от ручного выделения и освобождения памяти, занятой объектами. Политики автоматического управления памятью реализованы в сборщике мусора. Далее представлен жизненный цикл объекта управления памятью.

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

· Если объект или его любая часть не могут быть вызваны посредством любой возможной продолжительности выполнения, отличной от выполнения деструкторов, объект считается неиспользуемым и становится доступным для деструкции. Компилятор C# и сборщик мусора могут анализировать код с целью определения того, какие ссылки на объект могут быть использованы в будущем. Например, если локальная переменная в области является единственной существующей ссылкой на объект, но на нее никогда не ссылались в любом возможном предложении выполнения, начиная с текущего момента выполнения в процедуре, сборщик мусора может (но не обязательно) посчитать данный объект неиспользуемым.

· После того, как объект становится доступным для деструкции, через некоторое указанное время выполняется деструктор (§10.13) (при наличии) для объекта. При отсутствии иных явных вызовов деструктор для объекта выполняется только один раз.

· Если после выполнения деструктора для объекта данный объект или любая его часть не доступны для любого продолжения выполнения, включая выполнение деструкторов, объект считается недоступным и становится доступным для коллекции.

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

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

Подробно другим языкам, допускающим наличие сборщика мусора, C# разработан таким образом, чтобы сборщик мусора мог реализовывать широкий спектр политик управления памятью. Например, C# не требует, чтобы осуществлялись выполнение деструкторов или подборка объектов сразу после того, как они становятся доступными, или выполнение деструкторов в любом определенном порядке или в любом определенном потоке.

Поведение сборщика мусора можно в некоторой степени контролировать посредством статических методов класса System.GC. Этот класс может использоваться для запроса выполнения сборки, выполнения деструкторов (или отмены выполнения) и т. п.

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

using System;

class A
{
~A() {
Console.WriteLine("Destruct instance of A");
}
}

class B
{
object Ref;

public B(object o) {
Ref = o;
}

~B() {
Console.WriteLine("Destruct instance of B");
}
}

class Test
{
static void Main() {
B b = new B(new A());
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}

приводит к созданию экземпляра класса A и экземпляра класса B. Данные объекты становятся доступны сборщику мусора, когда переменной b присваивается значение null, так как после этого доступ к ним невозможен посредством любого пользовательского кода. Результат может быть одним из следующих:

Destruct instance of A (Деструкция экземпляра A)
Destruct instance of B (Деструкция экземпляра A)

или

Destruct instance of B
Destruct instance of A

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

В особых случаях отличие между «доступен для деструкции» и «доступен для сборки» может быть очень важным. Например,

using System;

class A
{
~A() {
Console.WriteLine("Destruct instance of A");
}

public void F() {
Console.WriteLine("A.F");
Test.RefA = this;
}
}

class B
{
public A Ref;

~B() {
Console.WriteLine("Destruct instance of B");
Ref.F();
}
}

class Test
{
public static A RefA;
public static B RefB;

static void Main() {
RefB = new B();
RefA = new A();
RefB.Ref = RefA;
RefB = null;
RefA = null;

// A и B теперь доступны для деструкции
GC.Collect();
GC.WaitForPendingFinalizers();

// B теперь доступен для сборки, а A нет
if (RefA != null)
Console.WriteLine("RefA is not null");
}
}

В вышеуказанной программе, если сборщик мусора выбирает выполнение деструктора A до деструктора B, то результат программы может быть следующим:

Destruct instance of A
Destruct instance of B
A.F
RefA is not null (RefA не имеет значения null)

Обратите внимание, что, несмотря на то, что экземпляр A не был использован, а деструктор A был выполнен, методы A (в данном случае F) могут быть все равно вызваны из другого деструктора. Также обратите внимание, что выполнение деструктора может привести к тому, что объект станет снова доступен для использования из основной программы. В этом случае выполнение деструктора B привело к тому, что экземпляр A, который ранее не был использован, стал доступен из существующей ссылки Test.RefA. После вызова WaitForPendingFinalizers экземпляр B доступен для сборки, а экземпляр A недоступен для сборки из-за ссылки Test.RefA.

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

Альтернативой использованию деструкторов может служить реализация классом интерфейса System.IDisposable. Это позволит клиенту объекта определить момент освобождения ресурсов объекта, что обычно происходит путем вызова объекта в качестве ресурса с помощью оператора using (§8.13).

3.10 Порядок выполнения

Выполнение программы на языке C# выполняется таким образом, что побочные эффекты каждого выполняемого потока сохраняются в критических точках выполнения. Побочный эффект — это чтение или запись поля с модификатором volatile, запись в переменную без модификатора volatile, запись во внешний ресурс и передача исключения. Критические точки выполнения, в которых сохраняется порядок данных побочных эффектов, ссылаются на поля с модификаторами volatile (§10.5.3), операторы lock (§8.12) и создание и завершение потока. Среда выполнения может изменить порядок выполнения программы на C#, учитывая следующие ограничения.

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

· Правила упорядочивания инициализации сохраняются (§10.5.4 и §10.5.5).

· Упорядочивание побочных эффектов сохраняется с учетом чтения и записи модификатора volatile (§10.5.3). Кроме того, среда выполнения не должна вычислять часть выражения, если невозможно определить, что значение данного выражения не используется, и что необходимые побочные эффекты не имели места (включая вызванное при вызове метода или доступе к полю с модификатором volatile). При прерывании выполнения программы асинхронным событием (например, при передаче исключения другим потоком) оригинальный программный порядок отображения наблюдаемых побочных эффектов не гарантируется.


4. Типы

В языке C# представлены две основные категории типов: типы значений и ссылочные типы. Оба типа значения и ссылочные типы могут использоваться как универсальные типы, принимающие один или несколько параметров типа. Параметры типа могут обозначать как типы значений, так и ссылочные типы.

тип:
тип_значений
ссылочный_тип
параметр_типа

Типы третьей категории — указатели — доступны только в небезопасном коде. Дополнительные сведения см. далее в разделе §18.2.

Различие между типами значений и ссылочными типами заключается в том, что переменные первого типа непосредственно содержат данные, а переменные второго типа хранят ссылки на соответствующие данные (объекты). Две переменные ссылочного типа могут ссылаться на один объект. Это позволяет изменять объект, на который ссылается одна переменная, выполняя соответствующие операции с другой. Каждая переменная типа значений содержит собственную копию данных. В связи с этим операции с одной переменной не влияют на другую.

В C# применяется унифицированная система типов, в которой значение любого типа может обрабатываться как объект. В C# каждый тип прямо или косвенно наследует от типа класса object, который является первичным базовым классом для всех типов. Обработка значений ссылочного типа как объектов выполняется посредством рассмотрения их как значений типа object. Значения типа значений обрабатываются как объекты с использованием операций упаковки и распаковки (§4.3).

4.1 Типы значений

Существуют следующие типы значений: тип структуры и перечисляемый тип. В C# представлен набор предопределенных типов структуры, называемых простыми типами. Простые типы обозначаются зарезервированными словами.

тип_значений:
тип_структуры
перечисляемый_тип

тип_структуры:
имя_типа
простой_тип
обнуляемый_тип

простой_тип:
числовой_тип
bool

числовой_тип:
целый_тип
тип_с_плавающей_запятой
decimal

целый_тип:
sbyte
byte
short
ushort
int
uint
long
ulong
char

тип_с_плавающей_запятой:
float
double

обнуляемый_тип:
необнуляемый_тип_значений ?

необнуляемый_тип_значений:
тип

перечисляемый_тип:
имя_типа

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

При присвоении переменной типа значений создается копия присваиваемого значения. В этом заключается отличие от переменных ссылочного типа, при присвоении которых копируются ссылки, а не определяемые ими объекты.

System.ValueType

Обратите внимание, что…

4.1.2 Конструкторы по умолчанию

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

· Для всех простых_типов значение по умолчанию представляет собой битовый шаблон, заполненный нулями:

o Для типов sbyte, byte, short, ushort, int, uint, long и ulong значение по умолчанию составляет 0.

o Для типа char значение по умолчанию составляет '\x0000'.

o Для типа float значение по умолчанию составляет 0.0f.

o Для типа double значение по умолчанию составляет 0.0d.

o Для типа decimal значение по умолчанию составляет 0.0m.

o Для типа bool значение по умолчанию составляет false.

· Для перечисляемого_типа E значение по умолчанию составляет 0 и преобразуется к типу E.

· Для типа_структуры значение по умолчанию формируется путем присвоения значений по умолчанию всем полям, имеющим тип значений, а всем полям ссылочного типа — значения null.

· Для обнуляемого_типа значение по умолчанию представляет собой экземпляр, свойство HasValue которого имеет значение «false», а свойство Value не определено. Для обнуляемых типов значение по умолчанию также называется пустым значением.

Как и любой другой конструктор экземпляров, конструктор по умолчанию для типа значений вызывается с помощью оператора new. В целях повышения эффективности в реализации не обязательно создается вызов конструктора. В приведенном ниже примере выполняется инициализация переменных i и j нулевыми значениями.

class A
{
void F() {
int i = 0;
int j = new int();
}
}

Поскольку для каждого типа значений неявно определяется не содержащий параметров открытый конструктор экземпляров, в типе структуры не допускается явное объявление не содержащего параметров конструктора. Тем не менее, в типе структуры допускается объявление параметризованных конструкторов экземпляров (§11.3.8).

4.1.3 Типы структуры

Тип структуры представляет собой тип значений, в котором допускается объявление констант, полей, методов, свойств, индексаторов, операторов, конструкторов экземпляров, статических конструкторов и вложенных типов. Дополнительные сведения об объявлении типов структуры см. в разделе §11.1.

4.1.4 Простые типы

В C# представлен набор предопределенных типов структуры, называемых простыми типами. Простые типы обозначаются зарезервированными словами. Эти слова представляют собой псевдонимы предопределенных типов структуры в пространстве имен System (см. таблицу ниже).

 

Зарезервированное слово Тип с псевдонимом
sbyte System.SByte
byte System.Byte
short System.Int16
ushort System.UInt16
int System.Int32
uint System.UInt32
long System.Int64
ulong System.UInt64
char System.Char
float System.Single
double System.Double
bool System.Boolean
decimal System.Decimal

 

Поскольку простой тип представляет собой псевдоним типа структуры, каждый простой тип содержит члены. Например, int содержит члены, объявленные в System.Int32, а также члены, унаследованные от System.Object. При этом допускаются операторы следующего вида:

int i = int.MaxValue; // System.Int32.MaxValue constant
string s = i.ToString(); // System.Int32.ToString() instance method
string t = 123.ToString(); // System.Int32.ToString() instance method

Различие между простыми типами и типами структуры заключается в том, что в простых типах допускаются некоторые дополнительные операции:

· Для большинства простых типов допускается создание значений путем записи литералов (§2.4.4). Например, 123 представляет собой литерал типа int, а 'a' — литерал типа char. В C# не предусматривается использование литералов для типов структуры. Создание значений не по умолчанию для других типов структуры практически во всех случаях осуществляется с использованием конструкторов экземпляров соответствующих типов.

· Значение выражения, все операнды которого являются константами простого типа, может быть вычислено на этапе компиляции. Такие выражения называются константными_выражениями (§7.18). Выражения, которые содержат операторы, определяемые другими типами структуры, не являются константными.

· Для объявления констант простых типов используется объявление const (§10.4). Константы других типов структуры не поддерживаются, однако аналогичное действие имеют поля static readonly.

· При вычислении операторов преобразования, определенных другими типами структуры, могут использоваться преобразования простых типов. Однако пользовательские операторы преобразования никогда не используются при вычислении другого пользовательского оператора (§6.4.3).

4.1.5 Целые типы

В C# поддерживается девять целых типов: sbyte, byte, short, ushort, int, uint, long, ulong и char. Целые типы имеют следующие размеры и диапазоны значений:

· Тип sbyte представляет 8-разрядные целые числа со знаком в диапазоне от –128 до 127.

· Тип byte представляет 8-разрядные целые числа без знака в диапазоне от 0 до 255.

· Тип short представляет 16-разрядные целые числа со знаком в диапазоне от –32768 до 32767.

· Тип ushort представляет 16-разрядные целые числа без знака в диапазоне от 0 до 65535.

· Тип int представляет 32-разрядные целые числа со знаком в диапазоне от –2147483648 до 2147483647.

· Тип uint представляет 32-разрядные целые числа без знака в диапазоне от 0 до 4294967295.

· Тип long представляет 64-разрядные целые числа со знаком в диапазоне от –9223372036854775808 до 9223372036854775807.

· Тип ulong представляет 64-разрядные целые числа без знака в диапазоне от 0 до 18446744073709551615.

· Тип char представляет 16-разрядные целые числа без знака в диапазоне от 0 до 65535. Набор возможных значений для типа char соответствует набору символов Юникода. Несмотря на то, что представления типов char и ushort совпадают, наборы допустимых операций для каждого типа различаются.

Унарные и бинарные операторы целого типа всегда работают с точностью, соответствующей 32- или 64-разрядным числам со знаком или без знака:

· Для унарных операторов «+» и «~» операнд преобразуется к типу T, где T — первый тип из набора int, uint, long и ulong, с помощью которого могут быть полностью представлены все возможные значения операнда. Операция выполняется с использованием точности, соответствующей типу T. Результат операции имеет тип T.

· Для унарного оператора «–» операнд преобразуется к типу T, где T — первый тип из набора int и long, с помощью которого могут быть полностью представлены все возможные значения операнда. Операция выполняется с использованием точности, соответствующей типу T. Результат операции имеет тип T. Не допускается применение унарного оператора «–» к операндам типа ulong.

· Для бинарных операторов +, –, *, /, %, &, ^, |, ==, !=, >, <, >= и <= операнды преобразуются к типу T, где T — первый тип из набора int, uint, long и ulong, с помощью которого могут быть полностью представлены все возможные значения обоих операндов. Операция выполняется с использованием точности, соответствующей типу T. Результат операции имеет тип T (или bool для операторов отношения). Для бинарных операторов не допускается использование двух переменных различных типов (например long и ulong).

· Для бинарных операторов << и >> левый операнд преобразуется к типу T, где T — первый тип из набора int, uint, long и ulong, с помощью которого могут быть полностью представлены все возможные значения операнда. Операция выполняется с использованием точности, соответствующей типу T. Результат операции имеет тип T.

Тип char классифицируется как целый тип, однако имеет два отличия от других целых типов:

· Не поддерживается неявное преобразование из других типов к типу char. В частности, несмотря на то, что диапазоны значений для типов sbyte, byte и ushort могут быть полностью представлены с помощью типа char, неявное преобразование из типов sbyte, byte и ushort к типу char не существует.

· Константы типа char должны записываться в виде символьных или целочисленных_литералов в сочетании с приведением к типу char. Например, записи (char)10 и '\x000A' аналогичны.

Для управления проверкой переполнения при выполнении целочисленных арифметических операций и преобразований используются операторы checked и unchecked (§7.5.12). В контексте checked при переполнении возникает ошибка времени компиляции или порождается исключение System.OverflowException. В контексте unchecked переполнение игнорируется, а все старшие биты, не соответствующие размеру конечного типа, удаляются.

4.1.6 Типы с плавающей запятой

В C# поддерживается два типа с плавающей запятой: float и double. Типы float и double представляются в 32-разрядном (одинарная точность) или 64-разрядном (двойная точность) формате IEEE 754 и поддерживают следующие наборы значений:

· Положительный и отрицательный нуль. В большинстве случаев поведение значений положительного и отрицательного нуля совпадает. Различие между ними используется лишь в некоторых операторах (§7.7.2).

· Положительная и отрицательная бесконечность. Бесконечность может получиться, например, в результате деления ненулевого значения на нуль. К примеру, в результате операции 1.0 / 0.0 получается положительная бесконечность, а в результате операции –1.0 / 0.0 отрицательная.

· Нечисловые значения зачастую сокращаются как NaN. Значения NaN получаются в результате выполнения недопустимых операций с плавающей запятой, например при делении нуля на нуль.

· Конечный набор ненулевых значений вида s x m x 2e, где s равно 1 или −1, а значения m и e зависят от конкретного типа с плавающей запятой, выглядит для типа float как 0 < m < 224 и −149 ≤ e ≤ 104, для типа double как 0 < m < 253 и −1075 ≤ e ≤ 970. Допустимыми ненулевыми значениями считаются денормализованные числа с плавающей запятой.

Тип float представляет значения в диапазоне от 1,5 x 10−45 до 3,4 x 1038 (приблизительно) с точностью до 7 знаков.

Тип double представляет значения в диапазоне от 5,0 x 10−324 до 1,7 x 10308 (приблизительно) с точностью до 15–16 знаков.

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

· Если один из операторов имеет целый тип, он преобразуется к типу с плавающей запятой, соответствующему типу второго операнда.

· В этом случае, если один из операндов имеет тип double, второй операнд преобразуется к типу double. Операция выполняется с применением точности и диапазона, соответствующих типу double. Результат операции имеет тип double (или bool для операторов отношения).

· В противном случае операция выполняется с применением точности и диапазона, соответствующих типу float. Результат операции имеет тип float (или bool для операторов отношения).

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

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

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

· Если при выполнении операции с плавающей запятой получается недопустимый результат, операция возвращает результат NaN.

· Если один или оба операнда операции с плавающей запятой имеют значение NaN, операция возвращает результат NaN.

Операции с плавающей запятой могут выполняться с точностью, превышающей точность типа результата операции. Например, в некоторых аппаратных архитектурах поддерживаются типы с плавающей запятой «extended» или «long double», обладающие расширенными диапазоном и точностью по сравнению с типом double. В таких случаях все операции с плавающей запятой неявно выполняются с использованием типа с более высокой точностью. В таких аппаратных архитектурах операции с плавающей запятой выполняются с меньшей точностью только в целях повышения производительности (в случае ее значительного снижения). Чтобы не допустить одновременного снижения производительности и точности при реализации, в C# возможно применение типа с более высокой точностью для выполнения всех операций с плавающей запятой. Применение таких типов не дает какого-либо измеримого эффекта, за исключением получения более точного результата. Однако в выражениях вида x * y / z, в которых в результате умножения получается значение, выходящее за рамки диапазона типа double, но при последующем делении может быть получен временный результат, принадлежащий диапазону double, вычисление выражения с более высокой точностью позволяет получить определенный результат вместо бесконечности.

Decimal

Тип decimal определяет… Если один из… В результате…

Bool

Тип bool представляет логические величины. Тип bool поддерживает два возможных значения: true и false.

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

В языках C и C++ нулевые целочисленные значения или значения с плавающей запятой, а также пустые указатели могут быть преобразованы в логическое значение false. Ненулевые целочисленные значения или значения с плавающей запятой и непустые указатели могут быть преобразованы в логическое значение true. В C# такие преобразования выполняются посредством явного сравнения целочисленного значения или значения с плавающей запятой с нулем, а также посредством явного сравнения ссылки на объект со значением null.

4.1.9 Перечисляемые типы

Перечисляемые типы содержат конечное число значений, представляющих собой именованные константы. Каждый перечисляемый тип является производным от одного из следующих базовых типов: byte, sbyte, short, ushort, int, uint, long или ulong. Набор значений перечисляемого типа соответствует набору значений базового типа. Возможные значения перечисляемого типа не ограничиваются значениями именованных констант. Определение перечисляемых типов выполняется посредством объявлений перечисления (§14.1).

4.1.10 Обнуляемые типы

Обнуляемые типы могут представлять все значения базового типа и дополнительное пустое значение. Обнуляемый тип обозначается как T?, где T — базовый тип. Данный синтаксис представляет собой сокращенное обозначение типа System.Nullable<T>. Эти синтаксические формы являются взаимозаменяемыми.

Напротив, необнуляемый тип значений представляет собой тип значений, отличный от System.Nullable<T> или его сокращенной формы T? (для любого типа T), плюс любой параметр типа, который должен иметь необнуляемый тип значений (то есть любой параметр типа с ограничением struct). Тип System.Nullable<T> определяет ограничение типа значений для типа T (§10.1.5). Это означает, что в качестве базового для обнуляемого типа может использоваться любой необнуляемый тип значений. Обнуляемые или ссылочные типы не могут использоваться в качестве базовых для обнуляемого типа. Например, int?? и string? являются недопустимыми типами.

Экземпляр обнуляемого типа T? обладает двумя открытыми свойствами, доступными только для чтения:

· Свойство HasValue типа bool.

· Свойство Value типа T.

Экземпляр, свойство HasValue которого имеет значение true, считается непустым. Свойство Value непустого экземпляра содержит возвращаемое значение.

Экземпляр, свойство HasValue которого имеет значение false, считается пустым. Пустой экземпляр имеет неопределенное значение. При попытке чтения свойства Value пустого экземпляра порождается исключение System.InvalidOperationException. Процесс доступа к свойству Value обнуляемого экземпляра называется развертыванием.

Помимо конструктора по умолчанию, для каждого обнуляемого типа T? предусмотрен открытый конструктор, принимающий один аргумент типа T. При вызове конструктора со значением x типа T в виде

new T?(x)

создается непустой экземпляр типа T?, свойству Value которого присваивается значение x. Процесс создания непустого экземпляра обнуляемого типа с использованием заданного значения называется свертыванием.

Доступны неявные преобразования литерала null к типу T? (§6.1.5) и типа T к T? (§6.1.4).

4.2 Ссылочные типы

Предусмотрены следующие виды ссылочных типов: тип класса, тип интерфейса, тип массива и тип делегата.

ссылочный_тип:
тип_класса
тип_интерфейса
тип_массива
тип_делегата

тип_класса:
имя_типа
object
string

тип_интерфейса:
имя_типа

тип_массива:
тип_не_массива спецификации_ранга

тип_не_массива:
тип

спецификации_ранга:
спецификация_ранга
спецификации_ранга спецификация_ранга

спецификация_ранга:
[ разделители_размерностейнеоб ]

разделители_размерностей:
,
разделители_размерностей ,

тип_делегата:
имя_типа

Значение ссылочного типа представляет собой ссылку на экземпляр типа (объект). Особое значение null совместимо со всеми ссылочными типами и обозначает отсутствие экземпляра.

4.2.1 Типы классов

Тип класса определяет структуру данных, которая содержит данные-члены (константы и поля), функции-члены (методы, свойства, события, индексаторы, операторы, конструкторы экземпляров, деструкторы и статические конструкторы), а также вложенные типы. Типы классов поддерживают механизм наследования, который позволяет создавать производные классы, расширяющие функциональные возможности базового класса. Экземпляры типов классов создаются с помощью выражений_создания_объекта (§7.5.10.1).

Дополнительные сведения о типах классов см. в разделе §10.

В языке C# некоторые предопределенные типы классов имеют особое значение (см. таблицу ниже).

 

Тип класса Описание
System.Object Первичный базовый класс для всех типов. См. §4.2.2.
System.String Строковый тип языка C#. См. §4.2.3.
System.ValueType Базовый класс для всех типов значений. См. §4.1.1.
System.Enum Базовый класс для всех перечисляемых типов. См.§14.
System.Array Базовый класс для всех типов массивов. См. §12.
System.Delegate Базовый класс для всех типов делегатов. См. §15.
System.Exception Базовый класс для всех типов исключений. См. §16.

 

4.2.2 Тип объекта

Тип класса object является первичным базовым классом для всех типов. В C# все типы прямо или косвенно наследуются от типа класса object.

Зарезервированное слово object представляет собой псевдоним предопределенного класса System.Object.

4.2.3 Строковый тип

Тип string представляет собой запечатанный тип класса, наследуемый непосредственно от класса object. Экземпляры класса string представляют собой строки, состоящие из символов Юникода.

Значения типа string могут быть записаны в виде строковых литералов (§2.4.4.5).

Зарезервированное слово string представляет собой псевдоним предопределенного класса System.String.

4.2.4 Типы интерфейса

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

Дополнительные сведения о типах интерфейса см. в разделе §13.

4.2.5 Типы массивов

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

Дополнительные сведения о типах массивов см. в разделе §12.

4.2.6 Типы делегатов

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

Ближайшим аналогом делегата в C и C++ является указатель на функцию. Различие между ними заключается в том, что указатель на функцию может содержать ссылки только на статические функции, тогда как делегат может содержать ссылки как на статические методы, так и на методы экземпляров. В последнем случае в делегате хранится не только ссылка на точку входа метода, но и ссылка на экземпляр объекта, для которого осуществляется вызов метода.

Дополнительные сведения о типах делегатов см. в разделе §15.

4.3 Упаковка и распаковка

Понятие упаковки и распаковки является ключевым в системе типов C#. С помощью данных операций осуществляется связь между типами_значений и ссылочными_типами за счет возможности преобразования любого значения типа_значений к типу object и обратно. С помощью упаковки и распаковки обеспечивается унифицированное представление системы типов, в котором значение любого типа может обрабатываться как объект.

4.3.1 Преобразования упаковки

Преобразование упаковки обеспечивает неявное преобразование типа_значений в ссылочный_тип. Предусмотрены следующие преобразования упаковки:

· Из любого типа_значений к типу object.

· Из любого типа_значений к типу System.ValueType.

· Из любого необнуляемого_типа_значений к любому типу_интерфейса, реализованному с помощью типа_значений.

· Из любого необнуляемого_типа к любому типу_интерфейса, реализованному с помощью базового типа для обнуляемого_типа.

· Из любого перечисляемого_типа к типу System.Enum.

· Из любого обнуляемого_типа, базовым для которого является перечисляемый_тип, к типу System.Enum.

Обратите внимание, что если неявное преобразование из параметра типа во время выполнения включает в себя преобразование из типа значений к ссылочному типу, оно будет выполнено как преобразование упаковки (§6.1.9).

Упаковка значения необнуляемого_типа_значений включает в себя выделение экземпляра объекта и копирование этого значения в указанный экземпляр.

Если выполняется упаковка значения null (свойство HasValue имеет значение false) обнуляемого_типа, то при этом создается пустая ссылка. В противном случае возвращается результат развертывания и упаковки базового значения.

Чтобы более наглядно представить процесс упаковки значения необнуляемого_типа_значений, предположим существование универсального класса упаковки, поведение которого могло бы определяться следующим объявлением:

sealed class Box<T>: System.ValueType
{
T value;

public Box(T t) {
value = t;
}
}

Упаковка значения v типа T будет включать в себя выполнение выражения new Box<T>(v) с последующим возвратом результата в виде значения типа object. Таким образом, операторы

int i = 123;
object box = i;

будут соответствовать операторам

int i = 123;
object box = i;

В действительности описанный выше класс упаковки Box<T> не существует, а динамический тип упакованного значения не является типом класса. Упакованное значение типа T имеет динамический тип T. При проверке динамического типа с помощью оператора is возможно использование ссылки на тип T. Например, в результате выполнения операторов

int i = 123;
object box = i;
if (box is int) {
Console.Write("Box contains an int");
}

на консоль будет выведена строка «Box contains an int».

Преобразование упаковки подразумевает создание копии упаковываемого значения. В этом заключается отличие от преобразования ссылочного_типа к типу object, при котором значение продолжает хранить ссылку на тот же экземпляр и считается производным значением типа object более низкого уровня. Например, при наличии следующего объявления

struct Point
{
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

в результате выполнения операторов

Point p = new Point(10, 10);
object box = p;
p.x = 20;
Console.Write(((Point)box).x);

на консоль будет выведено значение 10, поскольку при выполнении операции неявной упаковки (осуществляется при присвоении значения p свойству box) происходит копирование значения p. Если вместо Point объявить class, будет выведено значение 20, поскольку в этом случае значения p и box будут содержать ссылки на один и тот же экземпляр.

4.3.2 Преобразования распаковки

Преобразование распаковки обеспечивает явное преобразование ссылочного_типа к типу_значений. Предусмотрены следующие преобразования распаковки:

· Из типа object к любому типу_значений.

· Из типа System.ValueType к любому типу_значений.

· Из любого типа_интерфейса к любому необнуляемому_типу_значений, реализующему тип_интерфейса.

· Из любого типа_интерфейса к любому обнуляемому_типу, базовый тип которого реализует тип_интерфейса.

· Из типа System.Enum к любому перечисляемому_типу.

· Из типа System.Enum к любому обнуляемому_типу, базовым для которого является перечисляемый_тип.

Обратите внимание, что если явное преобразование из параметра типа во время выполнения включает в себя преобразование из ссылочного типа к типу значений, оно будет выполнено как преобразование распаковки (§6.2.6).

При выполнении операции распаковки для необнуляемого_типа_значений сначала проверяется, является ли экземпляр объекта упакованным значением указанного необнуляемого_типа_значений. После этого выполняется копирование значения из экземпляра.

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

Если рассматривать предполагаемый класс упаковки, описанный в предыдущем разделе, преобразование распаковки объекта box к типу_значений T будет включать в себя выполнение выражения ((Box<T>)box).value. Таким образом, операторы

object box = 123;
int i = (int)box;

будут соответствовать операторам

object box = new Box<int>(123);
int i = ((Box<int>)box).value;

Для успешного выполнения преобразования распаковки в указанный необнуляемый_тип_значений значение исходного операнда должно содержать ссылку на упакованное значение этого необнуляемого_типа_значений. Если исходный операнд имеет значение null, порождается исключение System.NullReferenceException. Если исходный операнд содержит ссылку на несовместимый объект, порождается исключение System.InvalidCastException.

Для успешного выполнения операции распаковки в заданный обнуляемый_тип исходный операнд должен иметь значение null или содержать ссылку на упакованное значение необнуляемого_типа_значений, являющегося базовым для обнуляемого_типа. Если исходный операнд содержит ссылку на несовместимый объект, порождается исключение System.InvalidCastException.

4.4 Сформированные типы

Объявление универсального типа определяет несвязанный универсальный тип, который используется в качестве шаблона для формирования различных типов посредством применения аргументов типа. Аргументы типа записываются с помощью угловых скобок («<» и «>») непосредственно после имени универсального типа. Тип, содержащий как минимум один аргумент типа, называется сформированным типом. Сформированный тип может использоваться в большинстве конструкций языка, в которых используется имя типа. Несвязанный универсальный тип может использоваться только в выражении_typeof (§7.5.11).

Сформированные типы также могут использоваться в выражениях в качестве простых имен (§7.5.2) или при доступе к членам (§7.5.4).

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

namespace Widgets
{
class Queue {...}
class Queue<TElement> {...}
}

namespace MyApplication
{
using Widgets;

class X
{
Queue q1; // неуниверсальный Widgets.Queue
Queue<int> q2; // универсальный Widgets.Queue
}
}

Имя_типа должно определять сформированный тип, даже если в нем прямо не заданы параметры типа. Это может произойти, если тип является вложенным в объявлении универсального класса, а тип экземпляра в содержащем объявлении неявно используется для поиска имен (§10.3.8.6):

class Outer<T>
{
public class Inner {...}

public Inner i; // типом i является Outer<T>.Inner
}

В небезопасном коде не допускается использование сформированного типа в качестве неуправляемого_типа (§18.2).

4.4.1 Аргументы типа

Каждый аргумент в списке аргументов типа представляет собой тип.

список_аргументов_типа:
< аргументы_типа >

аргументы_типа:
аргумент_типа
аргументы_типа , аргумент_типа

аргумент_типа:
тип

В небезопасном коде (§18) аргумент_типа не может являться типом указателя. Каждый аргумент типа должен удовлетворять любым ограничениям, накладываемым на соответствующий параметр типа (§10.1.5).

4.4.2 Открытые и закрытые типы

Все типы можно подразделить на открытые и закрытые типы. Открытый тип — это тип, в котором используются параметры типа. В частности:

· Параметр типа определяет открытый тип.

· Массив имеет открытый тип только в том случае, если тип элементов массива является открытым.

· Сформированный тип является открытым только в том случае, если один или несколько его аргументов типа имеют открытый тип. Вложенный сформированный тип является открытым только в том случае, если один или несколько аргументов типа для него или для содержащих его типов имеют открытый тип.

Все остальные типы являются закрытыми.

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

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

4.4.3 Связанные и несвязанные типы

Термин несвязанный тип обозначает не являющийся универсальным тип или несвязанный универсальный тип. Термин связанный тип обозначает не являющийся универсальным тип или сформированный тип.

Несвязанный тип ссылается на сущность, описанную в объявлении типа. Несвязанный универсальный тип сам по себе не является типом и не может использоваться в качестве типа переменной, аргумента или возвращаемого значения, а также в качестве базового типа. Несвязанный универсальный тип может использоваться только в выражениях typeof (§7.5.11).

4.4.4 Соблюдение ограничений

При каждом использовании сформированного типа или универсального метода выполняется проверка предоставленных аргументов типа на предмет соответствия ограничениям, накладываемым на параметры типа при объявлении универсального типа или метода (§10.1.5). Для каждого предложения where выполняется проверка аргумента типа A, соответствующего параметру именованного типа, следующим образом:

· Если ограничение представляет собой тип класса, тип интерфейса или параметр типа, определяется тип C, представляющий это ограничение. Предоставленные аргументы типа замещают любые параметры типа, указанные в ограничении. Соответствие ограничению соблюдается только в том случае, если тип A можно преобразовать к типу C с помощью любого из следующих преобразований:

o Преобразование идентификации (§6.1.1).

o Неявное преобразование ссылочного типа (§6.1.6)

o Преобразование упаковки (§6.1.7), если тип A представляет собой необнуляемый тип значений.

o Неявное преобразование ссылочного типа, упаковки или параметра типа из параметра типа A в C.

· Если ограничение является ограничением ссылочного типа (class), тип A должен соответствовать одному из следующих ограничений:

o Тип A является типом интерфейса, класса, делегата или массива. Обратите внимание, что типы System.ValueType и System.Enum являются ссылочными типами и соответствуют этому ограничению.

o Тип A представляет собой параметр типа, являющегося ссылочным типом (§10.1.5).

· Если ограничение является ограничением типа значений (struct), тип A должен соответствовать одному из следующих ограничений:

o Тип A является типом структуры или перечисляемым типом и не является обнуляемым типом. Обратите внимание, что типы System.ValueType и System.Enum являются ссылочными типами и не соответствуют этому ограничению.

o Тип A представляет собой параметр типа, для которого определено ограничение типа значений (§10.1.5).

· Если ограничение является ограничением конструктора new(), тип A не может представлять класс abstract и должен включать не содержащий параметров открытый конструктор. Соответствие этому ограничению обеспечивается в том случае, если верно одно из следующих утверждений:

o Тип A является типом значений, поскольку все типы значений содержат открытый конструктор по умолчанию (§4.1.2).

o Тип A представляет собой параметр типа, для которого определено ограничение конструктора (§10.1.5).

o Тип A представляет собой параметр типа, для которого определено ограничение типа значений (§10.1.5).

o Тип A представляет собой класс, отличный от abstract, и содержит явно объявленный конструктор public, не имеющий параметров.

o Тип A не представляет класс abstract и содержит конструктор по умолчанию (§10.11.4).

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

Поскольку наследование параметров типа не поддерживается, наследование ограничений также невозможно. В приведенном ниже примере требуется задать ограничение для параметра типа T типа D. При этом параметр T должен соответствовать ограничению, заданному в базовом классе B<T>. Напротив, для класса E не требуется задавать ограничение, поскольку List<T> реализует IEnumerable для любого T.

class B<T> where T: IEnumerable {...}

class D<T>: class B<T> where T: IEnumerable {...}

class E<T>: B<List<T>> {...}

4.5 Параметры типа

Параметр типа представляет собой идентификатор, определяющий тип значений или ссылочный тип, с которым параметр связан во время выполнения.

параметр_типа:
идентификатор

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

· Не допускается прямое использование параметра типа для объявления базового класса (§10.2.4) или интерфейса (§13.1.3).

· Правила поиска членов для параметров типа определяются применяемыми к ним ограничениями (при наличии таковых). Дополнительные сведения об ограничениях см. в разделе §7.3.

· Доступные преобразования для параметров типа определяются применяемыми к ним ограничениями (при наличии таковых). Дополнительные сведения о преобразованиях см. в разделах §6.1.9 и §6.2.6.

· Литерал null не может быть преобразован к типу, задаваемому параметром типа, за исключением случаев, когда параметр является ссылочным типом (§6.1.9). Вместо этого можно использовать выражение default (§7.5.13). Кроме того, если для параметра типа не предусмотрено ограничение типа значений, можно сравнить значение, которое имеет тип, задаваемый параметром типа, со значением null с помощью операторов «==» и «!=» (§7.9.6).

· Выражение new (§7.5.10.1) может использоваться только с параметром типа, для которого предусмотрено ограничение_конструктора или ограничение типа значений (§10.1.5).

· Параметр типа не может использоваться в атрибутах.

· Параметр типа не может использоваться при доступе к члену (§7.5.4) или имени типа (§3.8) для определения статического члена или вложенного типа.

· В небезопасном коде не допускается использование параметра типа в качестве неуправляемого_типа (§18.2).

Являясь типом, параметр типа представляет собой конструкцию, существующую исключительно во время компиляции. Во время выполнения каждый параметр типа связан с типом времени выполнения, заданным с помощью аргумента типа в объявлении универсального типа. Таким образом, переменная, объявленная с помощью параметра типа, во время выполнения будет иметь закрытый сформированный тип (§4.4.2). Во время выполнения для операторов и выражений, содержащих параметры типа, используются фактические типы, предоставленные в качестве аргументов типа для этих параметров.

4.6 Типы дерева выражений

Дерево выражений обеспечивает представление анонимных функций в виде структур данных вместо исполняемого кода. Дерево выражений представляет собой значение типа дерева выражения вида System.Linq.Expressions.Expression<D>, где D — любой тип делегата. Далее в этой спецификации такие типы будут обозначаться с помощью сокращенной формы Expression<D>.

Если для анонимной функции существует преобразование к типу делегата D, для нее также существует и преобразование к типу дерева выражений Expression<D>. В результате преобразования анонимной функции к типу делегата создается делегат, содержащий ссылки на исполняемый код этой функции. При преобразовании анонимной функции к типу дерева выражений создается представление этой функции в виде дерева выражений.

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

Тип Expression<D> обладает типами параметров и возвращаемых значений, аналогичными типу делегата D.

В приведенном ниже примере анонимная функция представляется как в виде исполняемого кода, так и в виде дерева выражений. Поскольку существует преобразование к Func<int,int>, также существует и преобразование к Expression<Func<int,int>>:

Func<int,int> del = x => x + 1; // код

Expression<Func<int,int>> exp = x => x + 1; // данные

В результате этих присвоений делегат del содержит ссылку на метод, который возвращает x + 1, а дерево выражений exp содержит ссылку на структуру данных, описывающую выражение x => x + 1.

Точное определение универсального типа Expression<D>, а также точные правила построения дерева выражений при преобразовании анонимной функции к типу дерева выражений описываются в соответствующей документации.

Следует обратить внимание на следующие моменты:

o Не все анонимные функции могут быть представлены в виде деревьев выражений. Например, не поддерживается представление анонимных функций, содержащих тела оператора и выражения присваивания. В этих случаях преобразование по-прежнему существует, однако вызывает ошибку во время компиляции.

o Тип Expression<D> содержит метод экземпляра Compile, порождающий делегат типа D:

Func<int,int> del2 = exp.Compile();

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

int i1 = del(1);

int i2 = del2(1);

После выполнения этого кода переменным i1 и i2 присваивается значение 2.

5. Переменные

Переменные указывают места хранения данных. Каждая переменная имеет тип, который определяет, какие значения могут храниться в этой переменной. В языке C# все данные должны относиться к определенному типу, компилятор C# обеспечивает соответствие типа значений, хранящихся в переменных. Значение переменной можно изменить путем присваивания или при помощи операторов ++ и - -.

Получить значение переменной можно только в том случае, если она является определенно присвоенной (§5.3).

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

5.1 Категории переменных

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

В этом примере

class A
{
public static int x;
int y;

void F(int[] v, int a, ref int b, out int c) {
int x = 1;
c = a + b++;
}
}

Переменная x является статической переменной, y — переменной экземпляра, v[0] — элементом массива, a — параметром по значению, b — параметром по ссылке, c — выходным параметром, а i — локальной переменной.

5.1.1 Статические переменные

Поле, объявленное с модификатором static, называется статической переменной. Статическая переменная создается перед выполнением статического конструктора (§10.12) для содержащегося в ней типа и прекращает свое существование при удалении домена связанного приложения.

Начальным значением статической переменной является значение, установленное по умолчанию для соответствующего типа переменных (§5.2).

Для целей проверки определенного присваивания статическая переменная считается переменной с начальным значением.

5.1.2 Переменные экземпляра

Поле, объявленное без модификатора static, называется переменной экземпляра.

5.1.2.1 Переменные экземпляра в классах

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

Начальным значением переменной экземпляра класса является значение, установленное по умолчанию для данного типа переменных (§5.2).

Для целей проверки определенного присваивания переменная экземпляра класса считается переменной с начальным значением.

5.1.2.2 Переменные экземпляра в структурах

Переменная экземпляра в структуре имеет такое же время жизни, как и переменная структуры, которой она принадлежит. Другими словами, переменные экземпляра в структуре создаются и удаляются при создании и удалении соответствующей структуры.

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

5.1.3 Элементы массива

Элементы массива создаются при создании экземпляра массива и удаляются при отсутствии ссылок на этот экземпляр массива.

Начальное значение каждого из элементов массива равно значению по умолчанию для элементов массива данного типа (§5.2).

Для целей проверки определенного присваивания элемент массива считается переменной с начальным значением.

5.1.4 Параметры по значению

Параметр, объявленный без модификатора ref или out, является параметром по значению.

Параметр по значению создается при вызове соответствующей функции-члена (метода, конструктора экземпляра, метода доступа или оператора) или анонимной функции, к которой относится данный параметр, и инициализируется по значению аргумента, заданного при вызове функции. Параметр по значению, как правило, удаляется при возвращении значения функцией-членом или анонимной функцией. Тем не менее, если параметр по значению перехвачен анонимной функцией (§7.14), его время жизни увеличивается на срок, пока делегат или дерево выражений, созданные из этой анонимной функции, доступны для сбора мусора.

Для целей проверки определенного присваивания параметр по значению считается переменной с начальным значением.

5.1.5 Параметры по ссылке

Параметр, объявленный с модификатором ref, является параметром по ссылке.

Использование параметра по ссылке не ведет к созданию нового места хранения. Параметр по ссылке указывает место хранения переменной, заданной в качестве аргумента при вызове функции-члена или анонимной функции. Таким образом, значение параметра по ссылке всегда совпадает со значением базовой переменной.

В отношении параметров по ссылке применяются указанные ниже правила определенного присваивания. Обратите внимание на разные правила для выходных параметров, описанные в §5.1.6.

· Переменная должна быть определенно присвоена (§5.3), чтобы ее можно было передать как параметр по ссылке при вызове функции-члена или делегата.

· В функции-члене или анонимной функции параметр по ссылки считается имеющим начальное значение.

В методе экземпляра или методе доступа экземпляра с типом struct ключевое слово this имеет точно такое же поведение, как параметр по ссылке с типом struct (§7.5.7).

5.1.6 Выходные параметры

Параметр, объявленный с модификатором out, является выходным параметром.

При использовании выходного параметра не создается новое место хранения. Выходной параметр указывает место хранения переменной, заданной в качестве аргумента при вызове функции-члена или делегата. Таким образом, значение выходного параметра всегда совпадает со значением базовой переменной.

В отношении выходных параметров применяются указанные ниже правила определенного присваивания. Обратите внимание на разные правила для выходных параметров, описанные в §5.1.5.

· Для передачи переменной как выходного параметра при вызове функции-члена или делегата определенно присваивать переменную необязательно.

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

· В функции-члене или анонимной функции выходной параметр считается не имеющим начального значения.

· Каждый выходной параметр функции-члена или анонимной функции должен быть определенно присвоен (§5.3) перед нормальным завершением выполнения такой функции-члена или анонимной функции.

В конструкторе экземпляра с типом struct ключевое слово this имеет точно такое же поведение, как выходной параметр с типом struct (§7.5.7).

5.1.7 Локальные переменные

Локальная переменная объявляется с использованием объявления_локальной_переменной, которое может находиться в блоке, операторе_for, операторе_switch или операторе_using, а также при помощи оператора_foreach или конкретной_конструкции_catch в операторе_try.

Время жизни локальной переменной соответствует части выполнения программы, в которой для нее гарантированно резервируется место. Это время жизни длится по меньшей мере с момента входа в блок, операторы for, switch, using, foreach или конкретную_конструцию_catch, с которой связана данная переменная, до завершения выполнения этого блока, операторов for, switch, using, foreach или конкретной_конструкции_catch любым из способов. (Вход во вложенный блок или вызов метода приостанавливает, но не завершает выполнение текущих блока, операторов for, switch, using, foreach или конкретной_конструкции_catch.) Если локальная переменная перехватывается анонимной функцией (§7.14.4.1), ее время жизни увеличивается по крайней мере на период времени, в течение которого делегат или дерево выражений, созданные в этой анонимной функции, а также другие объекты, ссылающиеся на перехваченную переменную, станут доступны для сбора мусора.

Если вход в родительский блок, оператор for, switch, using, foreach или конкретную_конструкцию_catch выполняется рекурсивно, каждый раз создается новый экземпляр локальной переменной, при этом каждый раз вычисляется инициализатор_локальной_переменной (при его наличии).

Локальная переменная, созданная с использованием объявления_локальной_переменной, не инициализируется автоматически и поэтому не имеет значения по умолчанию. Для целей проверки определенного присваивания локальная переменная, созданная с использованием объявления_локальной_переменной, считается не имеющей начального значения. Объявление_локальной_переменной может содержать инициализатор_локальной_переменной. В этом случае переменная считается определенно присвоенной по всей области действия за исключением выражения, представленного в инициализаторе_локальной_переменной.

В области действия локальной переменной, созданной с использованием объявления_локальной_переменной, ссылка на эту локальную переменную из фрагмента программы, предшествующего в тексте объявлению_локальной_переменной, приведет к ошибке компиляции. Если объявление локальной переменной выполнено неявно (§8.5.1), ссылка на эту переменную в ее объявлении_локальной_переменной также вызовет ошибку.

Локальная переменная, созданная с использованием оператора_foreach или конкретной_конструкции_catch, считается определенно присвоенной по всей области действия.

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

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

5.2 Значения по умолчанию

Указанные ниже категории переменных автоматически инициализируются соответствующими значениями по умолчанию.

· Статические переменные.

· Переменные экземпляра класса.

· Элементы массива.

Значение по умолчанию для переменной зависит от типа переменной и определяется следующим образом:

· Для переменной с типом_передаваемым_по_значению, значение по умолчанию равно значению, вычисленному конструктором по умолчанию для типа_передаваемого_по_значению (§4.1.2).

· Для переменной со ссылочным_типом значением по умолчанию является null.

При инициализации значениями по умолчанию, как правило, диспетчер памяти или сборщик мусора перед выделением области памяти инициализирует все ее биты нулями. По этой причине пустую ссылку удобно представить как область памяти, все биты которой равны нулю.

5.3 Определенное присваивание

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

· Переменная с начальным значением (§5.3.1) всегда считается определенно присвоенной.

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

o Простое присваивание (§7.16.1), в котором переменная является операндом слева.

o Выражение вызова (§7.5.5) или выражение создания объекта (§7.5.10.1), передающее переменную в качестве выходного параметра.

o Для локальной переменной — объявление локальной переменной (§8.5.1), включающее инициализатор переменной.

Формальная спецификация, определяющая изложенные выше правила, описана в §5.3.1, §5.3.2 и §5.3.3.

Определенное присваивание для переменных экземпляра переменной с типом_struct отслеживаются как по отдельности, так и в совокупности. В дополнение к изложенным выше правилам в отношении переменных с типом_struct и их переменных экземпляра применяются следующие правила:

· Переменная экземпляра считается определенно присвоенной, если содержащая ее переменная с типом_struct считается определенно присвоенной.

· Переменная с типом_struct считается определенно присвоенной, если каждая из ее переменных экземпляра считается определенно присвоенной.

Определенное присваивание является обязательным в следующих контекстах:

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

o переменная является левым операндом выражения простого присваивания,

o переменная передается в качестве выходного параметра, или

o переменная является переменной с типом_struct и указана как левый операнд метода доступа к члену.

· Переменная должна быть определенно присвоена в каждой точке, где она передается как параметр по ссылке. Это гарантирует, что вызываемая функция-член сможет использовать этот параметр по ссылке с начальным значением.

· Все выходные параметры функции-члена должны быть определенно присвоены в каждой точке, где выполняется возврат из функции-члена (при помощи оператора return или в результате достижения конца тела функции-члена). Это гарантирует, что функции-члены не будут возвращать в выходных параметрах неопределенные значения, что позволяет компилятору обработать вызов функции-члена, принимающей переменную в качестве выходного параметра, как выполнение присваивания в отношении этой переменной.

· Переменная this конструктора экземпляра переменной с типом_struct должна быть определенно присвоена в каждой точке, где выполняется возврат из этого конструктора экземпляра.

5.3.1 Переменные с начальным значением

К переменным с начальным значением относятся следующие категории переменных:

· Статические переменные.

· Переменные экземпляра для экземпляров классов.

· Переменные экземпляра переменных с типом struct с начальным значением.

· Элементы массива.

· Параметры по значению.

· Параметры по ссылке.

· Переменные, объявленные в предложении catch или операторе foreach.

5.3.2 Переменные без начального значения

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

· Переменные экземпляра переменных с типом struct без начального значения.

· Выходные параметры, включая переменную this конструкторов экземпляра переменных с типом struct.

· Локальные переменные, за исключением объявленных в предложении catch или операторе foreach.

5.3.3 Точные правила для выявления определенного присваивания

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

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

· В начале каждого оператора

· В конечной точке каждого оператора (§8.1)

· В каждой ветке, где управление передается в другой оператор или в конечную точку оператора

· В начале каждого выражения

· В конце каждого выражения

Состояние определенного присваивания переменной v может иметь одно из следующих значений:

· С определенным присваиванием. Указывает, что во всех возможных потоках управления в эту точку переменной v было присвоено значение.

· Без определенного присваивания. Состояние переменной без определенного присваивания в конце выражения с типом bool может (но не должно) иметь одно из следующих значений дополнительного состояния:

o Определенно присвоенная после выполнения выражения с итогом «true». Это состояние указывает, что переменная v является определенно присвоенной, если в результате выполнения логического выражения было получено значение «true», однако может не являться присвоенной, если в результате выполнения логического выражения было получено значение «false».

o Определенно присвоенная после выполнения выражения с итогом «false». Это состояние указывает, что переменная v является определенно присвоенной, если в результате выполнения логического выражения было получено значение «false», однако может не являться присвоенной, если в результате выполнения логического выражения было получено значение «true».

Определение состояния переменной v в каждой из точек управляется указанными ниже правилами.

5.3.3.1 Общие правила для операторов

· Переменная v не является определенно присвоенной в начале тела функции-члена.

· Переменная v является определенно присвоенной в начале каждой недостижимого оператора.

· Состояние определенного присваивания переменной v в начале каждого другого оператора определяется путем проверки состояния определенного присваивания переменной v во всех ветвлениях потоков управления, ведущих к началу этого оператора. Только в том случае, если переменная v является определенно присвоенной во всех таких ветвлениях потоков управления, эта переменная считается определенно присвоенной в начале оператора. Набор возможных ветвлений потоков управления определяется точно так же, как и при проверке достижимости операторов (§8.1).

· Состояние определенного присваивания переменной v в конечной точке блока или операторов checked, unchecked, if, while, do, for, foreach, lock, using или switch определяется путем проверки состояния определенного присваивания переменной v во всех ветвях потоков управления, ведущих к конечной точке этого оператора. Если переменная v является определенно присвоенной во всех ветвях потоков управления, эта переменная считается определенно присвоенной в конечной точке оператора. В обратном случае переменная v не является определенно присвоенной в конечной точке оператора. Набор возможных ветвлений потоков управления определяется точно так же, как и при проверке достижимости операторов (§8.1).

5.3.3.2 Операторы блока, проверенные и непроверенные операторы

Состояние определенного присваивания переменной v в ветви потока управления, ведущей к первому оператору в списке операторов блока (либо к конечной точке блока, если список операторов пуст) соответствует состоянию определенного присваивания переменной v перед блоком, оператором checked или unchecked.

5.3.3.3 Операторы выражений

Для оператора выражения stmt, состоящего из выражений expr:

· переменная v имеет такое же состояние определенного присваивания к началу выражения expr, как и к началу оператора stmt;

· Если переменная v является определенно присвоенной к концу выражения expr, она считается определенно присвоенной в конечной точке оператора stmt; в обратном случае она не является определенно присвоенной в конечной точке оператора stmt.

5.3.3.4 Операторы объявления

· Если оператор stmt является оператором объявления без инициализаторов, переменная v имеет такое же состояние определенного присваивания в конце оператора stmt, как и в начале этого оператора.

· Если оператор stmt является оператором объявления с инициализаторами, состояние определенного присваивания для переменной v определяется как для списка операторов с отдельным оператором присваивания для каждого объявления с инициализатором (в порядке объявления).

5.3.3.5 Операторы «If»

Для оператора if в форме:

if ( expr ) then-stmt else else-stmt

· переменная v имеет такое же состояние определенного присваивания на начало выражения expr, как и на начало оператора stmt;

· если переменная v является определенно присвоенной на конец выполнения выражения expr, она является определенно присвоенной в ветви потока управления до операции then-stmt, а также или в else-stmt, или в конечной точке операции stmt, если условие «else» отсутствует;

· если переменная v имеет состояние «определенно присвоенное в результате выполнения выражения с итогом true» в конце выражения expr, она считается определенно присвоенной в ветви потока управления до операции then-stmt и не является определенно присвоенной в ветви потока управления или в else-stmt, или в конечной точке операции stmt, если условие «else» отсутствует;

· если переменная v имеет состояние «определенно присвоенная после выполнения выражения с результатом false» в конце выражения expr, она является определенно присвоенной в ветви else-stmt и не является определенно присвоенной в ветви then-stmt. Эта переменная считается определенно присвоенной в конечной точке ветви stmt только в том случае, если она является определенно присвоенной в конечной точке then-stmt;

· в обратном случае переменная v не считается определенно присвоенной в ветви потока управления до операции then-stmt или else-stmt либо до конечной точки ветви stmt, если условие «else» отсутствует.

5.3.3.6 Операторы «switch»

В операторе switch для операции stmt с управляющим выражением expr:

· состояние определенного присваивания переменной v в начале выражения expr соответствует состоянию переменной v в начале операции stmt;

· состояние определенного присваивания переменной v в ветви потока управления до списка достижимых операторов блока «switch» соответствует состоянию определенного присваивания переменной v в конце выражения expr.

5.3.3.7 Операторы «while»

Для оператора while для операции stmt в форме:

while ( expr ) while-body

· переменная v имеет такое же состояние определенного присваивания в начале выражения expr, как и в начале операции stmt;

· если переменная v является определенно присвоенной в конце выражения expr, она является определенно присвоенной в ветви потока управления до операции while-body и до конечной точки операции stmt;

· если переменная v имеет состояние «определенно присвоенное в результате выполнения выражения с итогом true» в конце выражения expr, она является определенно присвоенной в ветви потока управления до операции while-body, однако не является определенно присвоенной в конечной точке операции stmt;

· если переменная v имеет состояние «определенно присвоенная в результате выполнения выражения с итогом false» в конце выражения expr, она является определенно присвоенной в ветви потока управления до конечной точки операции stmt, однако не является определенно присвоенной в ветви потока управления до операции while-body.

5.3.3.8 Операторы «do»

Для оператора do для операции stmt в форме:

do do-body while ( expr ) ;

· переменная v имеет такое же состояние определенного присваивания в ветви потока управления с начала операции stmt до операции do-body, как и в начале операции stmt.

· переменная v имеет такое же состояние определенного присваивания в начале выражения expr, как и в конечной точке операции do-body.

· если переменная v является определенно присвоенной в конечной точке expr, она является определенно присвоенной в ветви потока управления до конечной точки операции stmt.

· если переменная v имеет состояние «определенно присвоенная в результате выполнения выражения с итогом false» в конце выражения expr, она является определенно присвоенной в ветви потока управления до конечной точки stmt.

5.3.3.9 Операторы «for»

Проверка определенного присваивания для оператора for в форме

for ( for-initializer ; for-condition ; for-iterator ) embedded-statement

выполняется для следующей формы оператора:

{
for-initializer ;
while ( for-condition ) {
embedded-statement ;
for-iterator ;
}
}

Если условие_for в операторе for опущено, оценка определенного присваивания выполняется таким образом, словно условие_for в приведенной выше развернутой записи оператора было заменено значением true.

Break, continue и goto

Throw

Для оператора stmt в форме

throw expr ;

состояние определенного присваивания переменной v в начале выражения expr соответствует состоянию определенного присваивания переменной v в начале операции stmt.

Return

Для оператора stmt в форме

return expr ;

· Состояние определенного присваивания переменной v в начале выражения expr соответствует состоянию определенного присваивания переменной v в начале оператора stmt.

· Если переменная v является выходным параметром, она должна быть определенно присвоена в одном из следующих случаев:

o после выражения expr

o либо в конце блока finally конструкции try-finally или try-catch-finally, содержащей оператор return.

Для оператора stmt в форме

return ;

· Если переменная v является выходным параметром, она должна быть определенно присвоена в одном из следующих случаев:

o перед оператором stmt

o либо в конце блока finally конструкции try-finally или try-catch-finally, содержащей оператор return.

5.3.3.13 Операторы «try-catch»

Для оператора stmt в форме

try try-block
catch(...) catch-block-1
...
catch(...) catch-block-n

· Состояние определенного присваивания переменной v в начале try-block соответствует состоянию определенного присваивания переменной v в начале операции stmt.

· Состояние определенного присваивания переменной v в начале catch-block-i (для любой переменной i) соответствует состоянию определенного присваивания переменной v в начале операции stmt.

· Переменная v в конечной точке операции stmt считается определенно присвоенной только в том случае, если переменная v определенно присвоена в конечной точке try-block и каждого catch-block-i (для каждой переменной i от 1 до n).

5.3.3.14 Операторы «try-finally»

Для оператора stmt в форме

try try-block finally finally-block

· Состояние определенного присваивания переменной v в начале try-block соответствует состоянию определенного присваивания переменной v в начале операции stmt.

· Состояние определенного присваивания переменной v в начале finally-block соответствует состоянию определенного присваивания переменной v в начале операции stmt.

· Переменная v в конечной точке операции stmt считается определенно присвоенной только при соблюдении по меньшей мере одного из следующих условий:

o переменная v является определенно присвоенной в конечной точке try-block

o переменная v является определенно присвоенной в конечной точке finally-block

При переключении потока управления (например, в операторе goto), которое начинается в try-block и завершается вне его, переменная v также считается определенно присвоенной в такой ветви потока управления, если переменная v является определенно присвоенной в конечной точке finally-block. (Это не единственная возможность — если переменная v является определенно присвоенной по другой причине при таком переключении потока управления, она все еще считается определенно присвоенной.)

5.3.3.15 Операторы «try-catch-finally»

Анализ определенного присваивания для оператора try-catch-finally, имеющего форму

try try-block
catch(...) catch-block-1
...
catch(...) catch-block-n

выполняется с допущением, что оператор try-finally заключает оператор try-catch:

try {
try try-block
catch(...) catch-block-1
...
catch(...) catch-block-n
}
finally finally-block

В следующем примере демонстрируется, как разные блоки оператора try (§8.10) влияют на определенное присваивание.

class A
{
static void F() {
int i, j;
try {
goto LABEL;
// ни переменная i, ни переменная j не являются определенно присвоенными
i = 1;
// переменная i является определенно привоенной
}

catch {
// ни переменная i, ни переменная j не являются определенно присвоенными
i = 3;
// переменная i является определенно привоенной
}

finally {
// ни переменная i, ни переменная j не являются определенно присвоенными
j = 5;
// переменная j является определенно привоенной
}
// переменные i и j являются определенно привоенными
LABEL:;
// переменная j является определенно привоенной

}
}

5.3.3.16 Операторы «foreach»

Для оператора foreach stmt в форме

foreach ( type identifier in expr ) embedded-statement

· состояние определенного присваивания переменной v в начале выражения expr соответствуют состоянию переменной v в начале операции stmt.

· состояние определенного присваивания переменной v при переключении потока управления на внедренный_оператор или в конечную точку операции stmt соответствует состоянию переменной v в конце выражения expr.

5.3.3.17 Операторы «using»

Для оператора using операции stmt в форме

using ( приобретение_ресурсов ) внедренный_оператор

· состояние определенного присваивания переменной v в начале операции resource-acquisition соответствует состоянию переменной v в начале операции stmt.

· Состояние определенного присваивания переменной v при переключении потока управления на внедренный_оператор соответствует состоянию переменной v в конце операции приобретение_ресурсов.

5.3.3.18 Операторы «lock»

Для оператора lock операции stmt в форме

lock ( expr ) внедренный_оператор

· Состояние определенного присваивания переменной v в начале выражения expr соответствует состоянию переменной v в начале операции stmt.

· Состояние определенного присваивания переменной v при переключении потока управления на внедренный_оператор соответствует состоянию переменной v в конце выражения expr.

5.3.3.19 Операторы «yield»

Для оператора yield return операции stmt в форме

yield return expr ;

· состояние определенного присваивания переменной v в начале выражения expr соответствует состоянию v в начале операции stmt.

· состояние определенного присваивания переменной v в конце операции stmt соответствует состоянию переменной v в конце выражения expr.

· Оператор yield break не влияет на состояние определенного присваивания.

5.3.3.20 Общие правила для простых выражений

Указанное ниже правило применяется к следующим видам выражений: литералы (§7.5.1), простые имена (§7.5.2), выражения доступа к членам (§7.5.4), неиндексированные выражения базового доступа (§7.5.8), выражения typeof (§7.5.11) и выражения значений по умолчанию (§7.5.13).

· Состояние определенного присваивания переменной v в конце такого выражения соответствует состоянию определенного присваивания переменной v в начале этого выражения.

5.3.3.21 Общие правила для выражений с внедренными выражениями

Указанные ниже правила применяются к следующим видам выражений: выражения со скобками (§7.5.3), выражения доступа к элементам (§7.5.6), выражения базового доступа с индексацией (§7.5.8), выражения приращения и уменьшения (§7.5.9, §7.6.5), выражения приведения (§7.6.6), унарные выражения +, -, ~, *, двоичные выражения +, -, *, /, %, <<, >>, <, <=, >, >=, ==, !=, is, as, &, |, ^ (§7.7, §7.8, §7.9, §7.10), составные выражения присваивания (§7.16.2), выражения checked и unchecked (§7.5.12), а также выражения создания массивов и делегатов (§7.5.13).

Каждое из этих выражений имеет одно или несколько вложенных выражений, которые подлежат безусловному вычислению в фиксированном порядке. Например, в двоичном операторе % сначала вычисляется левая сторона оператора, а затем — правая сторона. В операции индексирования вычисляется индексированное выражение, а затем вычисляется каждое из выражений индекса в порядке слева направо. Выражение expr, которое имеет вложенные выражения expr1, expr2, ..., exprn, вычисляется в следующем порядке:

· Состояние определенного присваивания переменной v в начале выражения expr1 соответствует состоянию определенного присваивания в начале выражения expr.

· Состояние определенного присваивания переменной v в начале выражения expri (i больше единицы) соответствует состоянию определенного присваивания в конце выражения expri-1.

· Состояние определенного присваивания переменной v в конце выражения expr соответствует состоянию определенного присваивания в конце выражения exprn.

5.3.3.22 Выражения вызова и выражения создания объекта

Для выражения вызова expr в форме

первичное_выражение ( arg1 , arg2 , … , argn )

или выражения создания объекта в форме

new type ( arg1 , arg2 , … , argn )

· для выражения вызова состояние определенного присваивания переменной v перед первичным_выражением соответствует состоянию переменной v перед выражением expr;

· для выражения вызова состояние определенного присваивания переменной v перед arg1 соответствует состоянию переменной v после первичного_выражения;

· для выражения создания объекта состояние определенного присваивания переменной v перед arg1 соответствует состоянию переменной v перед expr;

· для каждого аргумента argi состояние определенного присваивания переменной v после argi определяется обычными правилами выражений с игнорированием модификаторов ref или out;

· для каждого аргумента argi при i больше единицы состояние определенного присваивания переменной v перед argi соответствует состоянию переменной v после argi-1;

· если переменная v передается в качестве аргумента out (например, аргумента в виде «out v») в любом из аргументов, переменная v после выражения expr является определенно присвоенной. В обратном случае состояние переменной v после expr соответствует состоянию переменной v после argn;

· для инициализаторов массива (§7.5.10.4), инициализаторов объектов (§7.5.10.2), инициализаторов коллекций (§7.5.10.3) и инициализаторов анонимных объектов (§7.5.10.6) состояние определенного присваивания определяется подстановкой, в контексте которой определяются эти конструкции.

5.3.3.23 Простые выражения присваивания

Для выражения expr в форме w = expr-rhs:

· состояние определенного присваивания переменной v перед выражением expr-rhs соответствует состоянию определенного присваивания переменной v перед выражением expr.

· если переменная w является той же самой переменной, что и переменная v, переменная v после выражения expr является определенно присвоенной. В обратном случае состояние определенного присваивания переменной v после выражения expr соответствует состоянию определенного присваивания переменной v после выражения expr-rhs.

5.3.3.24 Выражения &&

Для выражения expr в форме expr-first && expr-second:

· состояние определенного присваивания переменной v перед выражением expr-first соответствует состоянию определенного присваивания переменной v перед выражением expr;

· переменная v перед выражением expr-second является определенно присвоенной, если состояние переменной v после выражения expr-first является определенно присвоенным или «определенно присвоенным в результате выполнения выражения с итогом true». В обратном случае она не является определенно присвоенной;

· состояние определенного присваивания переменной v после выражения expr определяется следующими правилами:

o если переменная v после выражения expr-first является определенно присвоенной, переменная v после выражения expr также является определенно присвоенной;

o в обратном случае, если состояние переменной v после выражения expr-second является определенно присвоенным, а состояние переменной v после выражения expr-first соответствует состоянию «определенно присвоенная в результате выполнения выражения с итогом false», переменная v после выражения expr является определенно присвоенной;

o в обратном случае, если переменная v после выражения expr-second является определенно присвоенной или «определенно присвоенной в результате выполнения выражения с итогом true», переменная v после выражения expr является «определенно присвоенной в результате выполнения выражения с итогом true»;

o в обратном случае, если переменная v после выражения expr-first имеет состояние «определенно присвоенная в результате выполнения выражения с итогом false», а после выражения expr-second имеет состояние «определенно присвоенная в результате выполнения выражения с итогом false», то после выражения expr она имеет состояние «определенно присвоенная в результате выражения с итогом false»;

o в обратном случае переменная v после выражения expr не является определенно присвоенной.

Пример:

class A
{
static void F(int x, int y) {
int i;
if (x >= 0 && (i = y) >= 0) {
// i — определенно присвоенная
}
else {
// i — не определенно присвоенная
}
// i — не определенно присвоенная
}
}

В данном примере переменная i считается определенно присвоенной только в одном из внедренных операторов оператора if. В операторе if в методе F переменная i является определенно присвоенной в первом внедренном операторе, поскольку выполнение выражения (i = y) всегда предшествует выполнению этого внедренного оператора. Наоборот, переменная i не является определенно присвоенной во втором внедренном операторе, поскольку проверка условия x >= 0 может завершиться с итогом «false», в результате чего для переменной i не будет выполнено присваивание.

5.3.3.25 Выражения ||

Для выражения expr в форме expr-first || expr-second:

· состояние определенного присваивания переменной v перед выражением expr-first соответствует состоянию определенного присваивания переменной v перед выражением expr;

· переменная v перед выражением expr-second является определенно присвоенной, если состояние переменной v после выражения expr-first является определенно присвоенным или «определенно присвоенным в результате выполнения выражения с итогом false». В обратном случае она не является определенно присвоенной;

· состояние определенного присваивания переменной v после выражения expr определяется следующими факторами:

o если переменная v после выражения expr-first является определенно присвоенной, переменная v после выражения expr является определенно присвоенной;

o в обратном случае, если состояние переменной v после выражения expr-second является определенно присвоенным, а состояние переменной v после выражения expr-first соответствует состоянию «определенно присвоенная в результате выполнения выражения с итогом true», переменная v после выражения expr является определенно присвоенной;

o в обратном случае, если переменная v после выражения expr-second является определенно присвоенной или «определенно присвоенной в результате выполнения выражения с итогом false», переменная v после выражения expr является «определенно присвоенной в результате выполнения выражения с итогом false»;

o в обратном случае, если переменная v после выражения expr-first имеет состояние «определенно присвоенная в результате выполнения выражения с итогом true», а после выражения expr-second имеет состояние «определенно присвоенная в результате выполнения выражения с итогом true», переменная v после выражения expr имеет состояние «определенно присвоенная в результате выражения с итогом true»;

o в обратном случае переменная v после выражения expr не является определенно присвоенной.

Пример

class A
{
static void G(int x, int y) {
int i;
if (x >= 0 || (i = y) >= 0) {
// i — не определенно присвоенная
}
else {
// i — определенно присвоенная
}
// i — не определенно присвоенная
}
}

В данном примере переменная i считается определенно присвоенной только в одном из внедренных операторов оператора if. В операторе if в методе G переменная i является определенно присвоенной во втором внедренном операторе, поскольку выполнение выражения (i = y) всегда предшествует выполнению этого внедренного оператора. Наоборот, переменная i не является определенно присвоенной в первом внедренном операторе, поскольку проверка условия x >= 0 может завершиться с итогом «true», в результате чего переменной i не будет выполнено присваивание.

5.3.3.26 ! Выражения «!»

Для выражения expr в форме ! expr-operand:

· состояние определенного присваивания переменной v перед выражением expr-operand соответствует состоянию определенного присваивания переменной v перед выражением expr.

· Состояние определенного присваивания переменной v после выражения expr определяется следующими факторами:

o если переменная v после выражения expr-operand является определенно присвоенной, эта переменная после выражения expr также является определенно присвоенной;

o если переменная v после выражения expr-operand не является определенно присвоенной, эта переменная после выражения expr также не является определенно присвоенной;

o если переменная v после выражения expr-operand имеет состояние «определенно присвоенная в результате выполнения выражения с итогом false», эта переменная после выражения expr имеет состояние «определенно присвоенная в результате выполнения выражения с итогом true»;

o если переменная v после выражения expr-operand имеет состояние «определенно присвоенная в результате выполнения выражения с итогом true», эта переменная после выражения expr имеет состояние «определенно присвоенная в результате выполнения выражения с итогом false».

5.3.3.27 ?? Выражения

Для выражения expr в форме expr-first ?? expr-second:

· состояние определенного присваивания переменной v перед выражением expr-first соответствует состоянию определенного присваивания этой переменной перед выражением expr;

· состояние определенного присваивания переменной v перед выражением expr-second соответствует состоянию определенного присваивания этой переменной после выражения expr-first.

· состояние определенного присваивания переменной v после выражения expr определяется следующими факторами:

o если выражение expr-first является константным выражением (§7.18), имеющим значение null, состояние переменной v после выражения expr соответствует ее состоянию после выражения expr-second;

· В обратном случае состояние переменной v после выражения expr соответствует состоянию определенного присваивания этой переменной после выражения expr-first.

5.3.3.28 Выражения

Для выражения expr в форме expr-cond ? expr-true : expr-false:

· состояние определенного присваивания переменной v перед выражением expr-cond соответствует состоянию этой переменной перед выражением expr;

· переменная v перед выражением expr-true является определенно присвоенной только в том случае, если эта переменная после выражения expr-cond имеет состояние определенно присвоенной или «определенно присвоенной в результате выполнения выражения с итогом true»;

· переменная v перед выражением expr-false является определенно присвоенной только в том случае, если эта переменная после выражения expr-cond имеет состояние определенно присвоенной или «определенно присвоенной в результате выполнения выражения с итогом false»;

· состояние определенного присваивания переменной v после выражения expr определяется следующими факторами:

o если выражение expr-cond является константным выражением (§7.18), имеющим значение true, состояние переменной v после выражения expr соответствует состоянию этой переменной после выражения expr-true;

o в обратном случае, если выражение expr-cond является константным выражением (§7.18), имеющим значение false, состояние переменной v после выражения expr соответствует состоянию этой переменной после выражения expr-false;

o в обратном случае, если переменная v является определенно присвоенной как после выражения expr-true, так и после выражения expr-false, эта переменная после выражения expr также является определенно присвоенной;

o в обратном случае переменная v после выражения expr не является определенно присвоенной.

5.3.3.29 Анонимные функции

Для лямбда-выражений или выражений_ананимного_метода expr с телом, содержащим блок или выражение:

· Состояние определенного присваивания внешней переменной v перед телом body соответствует состоянию этой переменной перед выражением expr. Таким образом, состояние определенного присваивания внешних переменных наследуется из контекста анонимной функции.

· Состояние определенного присваивания внешней переменной v после выражения expr соответствует состоянию этой переменной перед выражением expr.

Пример:

delegate bool Filter(int i);

void F() {
int max;

// Ошибка, переменная max не является определенно присвоенной
Filter f = (int n) => n < max;

max = 5;
DoWork(f);
}

Этот пример приводит к возникновению ошибки времени компиляции, поскольку переменная max не является определенно присвоенной при объявлении анонимной функции. Пример:

delegate void D();

void F() {
int n;
D d = () => { n = 1; };

d();

// Ошибка, переменная n не является определенно присвоенной
Console.WriteLine(n);
}

Этот пример также приводит к возникновению ошибки времени компилирования, поскольку присваивание для переменной n в анонимной функции не влияет на состояние определенного присваивания переменной n вне анонимной функции.

5.4 Ссылочные переменные

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

ссылка_на_переменную:
выражение

В C и C++ ссылка_на_переменную называется lvalue.

5.5 Атомарность ссылок на переменные

К атомарным действиям относятся чтение и запись следующих типов данных: bool, char, byte, sbyte, short, ushort, uint, int, float и ссылочных типов. В дополнение к этому атомарными действиями являются чтение и запись типов перечислений с базовым типом в приведенном выше списке. Чтение и запись других типов, включая long, ulong, double и decimal, а также пользовательских типов, не обязательно являются атомарными действиями. Если речь не идет о предназначенных для этой цели библиотечных функциях, гарантия атомарного чтения, изменения и записи, например в случае приращения или уменьшения, отсутствует.


6. Преобразования

С помощью преобразований выражения могут обрабатываться как выражения определенного типа. Преобразования позволяют обрабатывать выражения заданного типа как имеющие другой тип, а выражения без типа — как выражения определенного типа. Преобразования бывают явными и неявными, что определяет необходимость использования явного приведения. Например, преобразование из типа int к типу long является неявным, поэтому выражения типа int могут неявно обрабатываться как имеющие тип long. Обратное преобразование из типа long к типу int является явным и требует явного приведения.

int a = 123;
long b = a; // неявное преобразование из int к long
int c = (int) b; // явное преобразование из long к int

Некоторые преобразования определяются языком. В программе также можно определить собственные преобразования (§6.4).

6.1 Неявные преобразования

К неявным преобразованиям относятся следующие:

· Преобразования идентификатора.

· Неявные преобразования числовых типов.

· Неявные преобразования перечисляемых типов.

· Неявные преобразования обнуляемых типов.

· Преобразования литерала null.

· Неявные преобразования ссылочных типов.

· Преобразования упаковки.

· Неявные преобразования выражений констант.

· Пользовательские неявные преобразования.

· Преобразования анонимных функций.

· Преобразования группы методов.

Неявные преобразования могут происходить во многих случаях, в том числе при вызове члена функции (§7.4.4), выражения приведения (§7.6.6) или присваивания (§7.16).

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

6.1.1 Преобразование идентификатора

Преобразование идентификатора обеспечивает приведение типа к такому же типу. Это преобразование используется, чтобы задать возможность преобразования к конкретному типу сущности, уже имеющей этот тип.

6.1.2 Неявные преобразования числовых типов

Существуют следующие неявные преобразования числовых типов:

· Из sbyte к short, int, long, float, double или decimal.

· Из byte к short, ushort, int, uint, long, ulong, float, double или decimal.

· Из short к int, long, float, double или decimal.

· Из ushort к int, uint, long, ulong, float, double или decimal.

· Из int к long, float, double или decimal.

· Из uint к long, ulong, float, double или decimal.

· Из long к float, double или decimal.

· Из ulong к float, double или decimal.

· Из char к ushort, int, uint, long, ulong, float, double или decimal.

· Из float к double.

Преобразования из типов int, uint, long или ulong к float, а также из long или ulong к double могут привести к потере точности, но не величины. Другие неявные преобразования числовых типов никогда не приводят к потере данных.

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

6.1.3 Неявные преобразования перечисляемых типов

Неявное преобразование перечисляемых типов обеспечивает преобразование литерала_целого_типа или типа_decimal 0 к любому перечисляемому_типу или к любому обнуляемому_типу, базовым для которого является перечисляемый_тип. В последнем случае выполняется преобразование к базовому перечисляемому_типу и последующее свертывание результата (§4.1.9).

6.1.4 Неявные преобразования обнуляемых типов

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

· Неявное преобразование из S? к T?.

· Неявное преобразование из S к T?.

Вычисление неявного преобразования обнуляемого типа, основанного на преобразовании из S к T, осуществляется следующим образом:

· Если выполняется преобразование из S? к T?:

o Если исходным значением является null (свойство HasValue имеет значение false), результатом является значение null типа T?.

o В противном случае преобразование вычисляется посредством развертывания из S? в S, преобразования из S к T и последующего свертывания (§4.1.9) из T к T?.

· Если выполняется преобразование обнуляемого типа из S к T?, оно вычисляется как базовое преобразование из S к T с последующим свертыванием из T в T?.

Null

Существует неявное преобразование литерала null к любому обнуляемому типу. В результате преобразования получается значение null (§4.1.9) заданного обнуляемого типа.

6.1.6 Неявные преобразования ссылочных типов

Существуют следующие неявные преобразования ссылочных типов:

· Из любого ссылочного_типа к типу object.

· Из любого типа_класса S к любому типу_класса T (где S является производным от T).

· Из любого типа_класса S к любому типу_интерфейса T (где S реализует T).

· Из любого типа_интерфейса S к любому типу_интерфейса T (где S является производным от T).

· Из типа_массива S, который имеет тип элементов SE, к типу_ массива T, который имеет тип элементов TE, если выполняются следующие условия:

o S и T различаются только по типу элементов. Другими словами, типы S и T имеют одинаковое число измерений.

o SE и TE являются ссылочными_типами.

o Существует неявное преобразование ссылочного типа из SE к TE.

· Из любого типа_массива к System.Array.

· Из одномерного типа массива S[] к System.Collections.Generic.IList<T> и его базовым интерфейсам, если существует неявное преобразование идентификатора или ссылочного типа из S к T.

· Из любого типа_делегата к System.Delegate.

· Из любого литерала null к любому ссылочному_типу.

· Неявные преобразования, включающие параметры типа, которые имеют ссылочный тип. Дополнительные сведения о неявных преобразованиях, включающих параметры типа, см. в §6.1.9.

Неявные преобразования ссылочных_типов всегда выполняются успешно и не требуют проверки во время выполнения.

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

В отличие от типов массивов, сформированные ссылочные типы не поддерживают «ковариантные» преобразования. Это означает, что тип List<B> не может быть преобразован (как явно, так и неявно) к типу List<A>, даже если B является производным от A. Аналогично, не существует преобразования из List<B> к List<object>.

6.1.7 Преобразования упаковки

Преобразование упаковки обеспечивает неявное преобразование типа_значений в ссылочный тип. Существует преобразование упаковки из любого необнуляемого_типа_значений к типу object, к System.ValueType, а также к любому типу_интерфейса, который реализуется необнуляемым_типом_значений. Кроме того, перечисляемый_тип может быть преобразован к типу System.Enum.

Преобразование упаковки из обнуляемого_типа к ссылочному типу существует только в том случае, если существует преобразование упаковки из базового необнуляемого_типа_значений к ссылочному типу.

Упаковка значения необнуляемого_типа_значений включает в себя выделение экземпляра объекта и копирование этого значения типа значений в указанный экземпляр. Структура может быть упакована в тип System.ValueType, поскольку он является базовым классом для всех структур (§11.3.2).

Упаковка значения обнуляемого_типа осуществляется следующим образом:

· Если исходным значением является null (свойство HasValue имеет значение false), результатом является значение null конечного типа.

· В противном случае результатом является ссылка на упакованное значение типа T, полученное в результате развертывания и упаковки исходного значения.

Дополнительные сведения о преобразованиях упаковки см. в §4.3.1.

6.1.8 Неявные преобразования выражений констант

Неявные преобразования выражений констант обеспечивают следующие преобразования:

· Преобразование константного_выражения (§7.18) типа int к типу sbyte, byte, short, ushort, uint или ulong, если значение константного_выражения является допустимым для конечного типа.

· Преобразование константного_выражения типа long к типу ulong, если значение константного_выражения не является отрицательным.

6.1.9 Неявные преобразования, включающие параметры типа

Существуют следующие неявные преобразования для заданного параметра типа T:

· Из T к эффективному базовому классу C, из T к любому базовому для C классу, а также из T к любому интерфейсу, реализованному классом C. Если T является типом значений, во время выполнения преобразование выполняется как преобразование упаковки. В противном случае преобразование выполняется как неявное преобразование ссылочного типа или идентификатора.

· Из T к типу интерфейса I, принадлежащему эффективному набору интерфейсов T, а также из T к любому базовому интерфейсу I. Если T является типом значений, во время выполнения преобразование выполняется как преобразование упаковки. В противном случае преобразование выполняется как неявное преобразование ссылочного типа или идентификатора.

· Из T к параметру типа U, если T зависит от U. Если T является типом значений, а U — ссылочным типом, во время выполнения преобразование выполняется как преобразование упаковки. В противном случае, если T и U имеют типы значений, типы T и U обязательно совпадают, и преобразование не выполняется. Если T имеет ссылочный тип, U также обязательно имеет ссылочный тип, и преобразование выполняется как неявное преобразование ссылочного типа или идентификатора.

· Из литерала null к T, если T является ссылочным типом.

Если T является ссылочным типом (§10.1.5), все описанные выше преобразования классифицируются как неявные преобразования ссылочных типов (§6.1.6). Если неизвестно, является ли T ссылочным типом, преобразования, описанные выше в первых двух пунктах списка, классифицируются как преобразования упаковки (§6.1.7).

6.1.10 Пользовательские неявные преобразования

Пользовательское неявное преобразование включает в себя необязательное стандартное неявное преобразование, за которым следует выполнение пользовательского оператора неявного преобразования, а затем другое необязательное стандартное неявное преобразование. Точные правила вычисления пользовательских неявных преобразований описываются в §6.4.4.

6.1.11 Преобразования анонимных функций и преобразования группы методов

Анонимные функции и группы методов не имеют типа, однако могут быть неявно преобразованы к типу делегата или типу дерева выражений. Дополнительные сведения о преобразованиях анонимных функций и группы методов см. в §6.5 и §6.6 соответственно.

6.2 Явные преобразования

К явным преобразованиям относятся следующие преобразования:

· Все неявные преобразования.

· Явные преобразования числовых типов.

· Явные преобразования перечисляемых типов.

· Явные преобразования обнуляемых типов.

· Явные преобразования ссылочных типов.

· Явные преобразования типов интерфейса.

· Преобразования распаковки.

· Пользовательские явные преобразования.

Явные преобразования могут произойти в выражениях приведения (§7.6.6).

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

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

6.2.1 Явные преобразования числовых типов

Явные преобразования числовых типов предназначены для преобразования из одного числового_типа к другому числовому_типу, для которого не существует неявного преобразования (§6.1.2):

· Из sbyte к byte, ushort, uint, ulong или char.

· Из byte к sbyte и char.

· Из short к sbyte, byte, ushort, uint, ulong или char.

· Из ushort к sbyte, byte, short или char.

· Из int к sbyte, byte, short, ushort, uint, ulong или char.

· Из uint к sbyte, byte, short, ushort, int или char.

· Из long к sbyte, byte, short, ushort, int, uint, ulong или char.

· Из ulong к sbyte, byte, short, ushort, int, uint, long или char.

· Из char к sbyte, byte или short.

· Из float к sbyte, byte, short, ushort, int, uint, long, ulong, char или decimal.

· Из double к sbyte, byte, short, ushort, int, uint, long, ulong, char, float или decimal.

· Из decimal к sbyte, byte, short, ushort, int, uint, long, ulong, char, float или double.

Поскольку явные преобразования включают в себя все неявные и явные преобразования числовых типов, всегда возможно преобразование из одного числового_типа к другому с помощью выражения приведения (§7.6.6).

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

· Для преобразований из одного целого типа в другой порядок выполнения зависит от контекста проверки переполнения (§7.5.12), в котором выполняется такое преобразование:

o В контексте checked преобразование выполняется успешно, если значение исходного операнда является допустимым для конечного типа. В противном случае порождается исключение System.OverflowException.

o В контексте unchecked преобразование всегда выполняется успешно следующим образом:

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

· Если размер исходного типа меньше размера конечного, размер исходного значения расширяется до размера конечного типа за счет добавления знака или нулей. Расширение знаком используется для исходных типов со знаком, расширение нулями — для исходных типов без знака. Результат обрабатывается как значение конечного типа.

· Если размеры исходного и конечного типов совпадают, исходное значение обрабатывается как значение конечного типа.

· Для преобразований из типа decimal к целому типу исходное значение округляется в сторону нуля до ближайшего целого значения. Результатом преобразования является полученное целое значение. Если результирующее целое значение находится вне диапазона конечного типа, порождается исключение System.OverflowException.

· Для преобразований из типа float или double к целому типу порядок выполнения зависит от контекста проверки переполнения (§7.5.12), в котором выполняется такое преобразование:

o В контексте checked преобразование осуществляется следующим образом:

· Если операнд имеет нечисловое значение или равен бесконечности, порождается исключение System.OverflowException.

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

· В противном случае порождается исключение System.OverflowException.

o В контексте unchecked преобразование всегда выполняется успешно следующим образом:

· Если операнд имеет нечисловое значение или равен бесконечности, результатом преобразования является неопределенное значение конечного типа.

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

· В противном случае в качестве результата преобразования возвращается неопределенное значение конечного типа.

· Для преобразования из double к float значение типа double округляется до ближайшего значения типа float. Если значение double слишком мало для представления в качестве float, в качестве результата возвращается положительный или отрицательный нуль. Если значение double слишком велико для представления в качестве float, в качестве результата возвращается положительная или отрицательная бесконечность. Если значение double является нечисловым, результат также является нечисловым значением.

· Для преобразования из float или double к типу decimal исходное значение преобразуется в представление decimal и при необходимости округляется до ближайшего числа после 28-го десятичного разряда (§4.1.7). Если исходное значение слишком мало для представления в качестве десятичного, в качестве результата возвращается нуль. Если исходное значение является нечисловым, бесконечностью или слишком велико для представления в виде decimal, порождается исключение System.OverflowException.

· Для преобразования из decimal к float или double значение типа decimal округляется до ближайшего значения типа double или float. Это преобразование может привести к потере точности, но никогда не порождает исключений.

6.2.2 Явные преобразования перечисляемых типов

Поддерживаются следующие явные преобразования перечисляемых типов:

· Из sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double или decimal к любому перечисляемому_типу.

· Из любого перечисляемого_типа к sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double или decimal.

· Из любого перечисляемого_типа к любому другому перечисляемому_типу.

Явное преобразование между двумя перечисляемыми типами выполняется посредством рассмотрения любого из участвующих перечисляемых_типов в качестве базового для этого перечисляемого_типа с последующим выполнением явного или неявного числового преобразования результирующих типов. Например, если для перечислеямого_типа E базовым является тип int, преобразование из E к типу byte выполняется как явное преобразование числового типа (§6.2.1) из int к byte, а преобразование из byte к E — как неявное преобразование числового типа (§6.1.2) из byte к int.

6.2.3 Явные преобразования обнуляемых типов

Явные преобразования обнуляемых типов позволяют использовать явные преобразования, выполняемые для необнуляемых типов значений, с обнуляемыми формами таких типов. Для каждого предопределенного явного преобразования из необнуляемого типа значений S к необнуляемому типу значений T (§6.1.1, §6.1.2, §6.1.3, §6.2.1 и §6.2.2) существуют следующие преобразования обнуляемых типов:

· Явное преобразование из S? к T?.

· Явное преобразование из S к T?.

· Явное преобразование из S? к T.

Вычисление преобразования обнуляемого типа, основанного на преобразовании из S к T, осуществляется следующим образом:

· Если выполняется преобразование из S? к T?:

o Если исходным значением является null (свойство HasValue имеет значение false), результатом является значение null типа T?.

o В противном случае преобразование вычисляется посредством развертывания из S? к S, преобразования из S к T и последующего свертывания из T к T?.

· Если выполняется преобразование обнуляемого типа из S к T?, оно вычисляется как базовое преобразование из S к T с последующим свертыванием из T в T?.

· Если выполняется преобразование обнуляемого типа из S? к T, оно вычисляется посредством развертывания из S? в S с последующим базовым преобразованием из S к T.

· Обратите внимание, что при попытке развертывания значения null обнуляемого типа порождается исключение.

6.2.4 Явные преобразования ссылочных типов

Поддерживаются следующие явные преобразования ссылочных типов:

· Из типа object к любому другому ссылочному_типу.

· Из любого типа_класса S к любому типу_класса T, если S является базовым классом для T.

· Из любого типа_класса S к любому типу_интерфейса T, если S не является запечатанным и не реализует T.

· Из любого типа_интерфейса S к любому типу_класса T, если T не является запечатанным или реализует S.

· Из любого типа_интерфейса S к любому типу_интерфейса T, если S не является производным от T.

· Из типа_массива S, который имеет тип элементов SE, к типу типу_массива T, который имеет тип элементов TE, если выполняются следующие условия:

o S и T различаются только по типу элементов. Другими словами, типы S и T имеют одинаковое число измерений.

o SE и TE являются ссылочными_типами.

o Существует явное преобразование ссылочного типа из SE к TE.

· Из System.Array и реализуемых им интерфейсов к любому типу_массива.

· Из одномерного типа массива S[] к System.Collections.Generic.IList<T> и его базовым интерфейсам, если существует явное преобразование ссылочного типа из S к T.

· Из System.Collections.Generic.IList<S> и его базовых интерфейсов к одномерному типу массива T[], если существует явное преобразование идентификатора или ссылочного типа из S к T.

· Из System.Delegate и реализуемых им интерфейсов к любому типу_делегата.

· Явные преобразования, включающие параметры типа, которые имеют ссылочный тип. Дополнительные сведения о явных преобразованиях, включающих параметры типа, см. в §6.2.6.

Для явных преобразований ссылочных типов требуется проверка корректности во время выполнения.

Для успешного выполнения явного преобразования ссылочного типа во время выполнения исходный операнд должен иметь значение null, или фактический тип объекта, на который ссылается исходный операнд, должен являться типом, который можно преобразовать в конечный тип с помощью неявного преобразования ссылочного типа (§6.1.6). В случае сбоя явного преобразования ссылочного типа порождается исключение System.InvalidCastException.

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

6.2.5 Преобразования распаковки

Преобразование распаковки обеспечивает явное преобразование ссылочного типа к типу_значений. Существуют преобразования распаковки из типов object и System.ValueType к любому необнуляемому_типу_значений, а также из любого типа_интерфейса к любому необнуляемому_типу_значений, реализующему такой тип_интерфейса. Кроме того, может быть выполнена распаковка System.Enum в любой перечисляемый_тип.

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

При выполнении операции распаковки сначала проверяется, является ли экземпляр объекта упакованным значением указанного типа_значений. После этого выполняется копирование значения из экземпляра. При распаковке пустой ссылки в обнуляемый_тип возвращается значение null обнуляемого_типа. Структура может быть распакована в тип System.ValueType, поскольку он является базовым классом для всех структур (§11.3.2).

Дополнительные сведения о преобразованиях распаковки см. в §4.3.2.

6.2.6 Явные преобразования, включающие параметры типа

Существуют следующие явные преобразования для заданного параметра типа T:

· Из эффективного базового для типа T класса C к T, а также из любого базового для C класса к T. Если T является типом значений, во время выполнения преобразование выполняется как преобразование распаковки. В противном случае преобразование выполняется как явное преобразование ссылочного типа или идентификатора.

· Из любого типа интерфейса к T. Если T является типом значений, во время выполнения преобразование выполняется как преобразование распаковки. В противном случае преобразование выполняется как явное преобразование ссылочного типа или идентификатора.

· Из T к любому типу_интерфейса I, если уже не существует неявного преобразования из T к I. Если T является типом значений, во время выполнения преобразование выполняется как преобразование упаковки с последующим явным преобразованием ссылочного типа. В противном случае преобразование выполняется как явное преобразование ссылочного типа или идентификатора.

· Из параметра типа U к T, если T зависит от U. Если T является типом значений, а U — ссылочным типом, во время выполнения преобразование выполняется как преобразование распаковки. В противном случае, если T и U имеют типы значений, типы T и U обязательно совпадают, и преобразование не выполняется. Если T имеет ссылочный тип, U также обязательно имеет ссылочный тип, и преобразование выполняется как явное преобразование ссылочного типа или идентификатора.

Если T является ссылочным типом, все описанные выше преобразования классифицируются как явные преобразования ссылочных типов (§6.2.4). Если неизвестно, является ли T ссылочным типом, преобразования, описанные выше в первых двух пунктах списка, классифицируются как преобразования распаковки (§6.2.5).

Приведенные выше правила не допускают прямого явного преобразования из безусловного параметра типа к типу, не являющемуся интерфейсом, поскольку его результат может быть непредсказуем. Это связано с необходимостью избежать несогласованности и достичь более точного представления семантики таких преобразований. Например, рассмотрим следующее объявление:

class X<T>
{
public static long F(T t) {
return (long)t; // Ошибка
}
}

Если разрешено прямое явное преобразование из t к int, можно предполагать, что выражение X<int>.F(7) возвратит 7L. Однако результат будет другим, поскольку стандартные преобразования числовых типов применяются только в том случае, если типы являются числовыми во время выполнения. Для более четкого определения семантики следует записать приведенный выше пример следующим образом:

class X<T>
{
public static long F(T t) {
return (long)(object)t; // Допустимо, но работает только если T имеет тип long
}
}

Код будет скомпилирован, однако при выполнении выражения X<int>.F(7) будет порождено исключение времени выполнения, поскольку упакованное значение int не может быть напрямую преобразовано в long.

6.2.7 Пользовательские явные преобразования

Пользовательское явное преобразование включает в себя необязательное стандартное явное преобразование, за которым следует выполнение пользовательского оператора явного преобразования, а затем другое необязательное стандартное явное преобразование. Точные правила вычисления пользовательских явных преобразований описываются в §6.4.5.

6.3 Стандартные преобразования

Стандартные преобразования — это предопределенные преобразования, которые могут произойти в составе пользовательских.

6.3.1 Стандартные неявные преобразования

К стандартным неявным преобразованиям относятся следующие преобразования:

· Преобразования идентификатора (§6.1.1).

· Неявные преобразования числовых типов (§6.1.2).

· Неявные преобразования обнуляемых типов (§6.1.4).

· Неявные преобразования ссылочных типов (§6.1.6).

· Преобразования упаковки (§6.1.7).

· Неявные преобразования выражений констант (§6.1.8).

· Неявные преобразования, включающие параметры типа (§6.1.9).

К стандартными неявным преобразованиям не относятся пользовательские неявные преобразования.

6.3.2 Стандартные явные преобразования

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

6.4 Пользовательские преобразования

В C# можно дополнить предопределенные явные и неявные преобразования пользовательскими преобразованиями. Пользовательские преобразования определяются посредством объявления операторов преобразования (§10.10.3) в типах класса и структуры.

6.4.1 Допустимые пользовательские преобразования

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

Предположим, что для заданных исходного типа S и конечного T (если S или T являются обнуляемыми типами) типы S0 и T0 являются соответствующими базовыми типами (в противном случае S0 и T0 считаются равными S и T соответственно). Объявление преобразования для класса или структуры из исходного типа S к конечному типу T допускается только в том случае, если выполняются все следующие условия:

· S0 и T0 являются разными типами.

· Либо тип S0, либо тип T0 является типом класса или структуры, в котором объявлен оператор.

· Ни тип S0, ни тип T0 не является типом_интерфейса.

· Не существует других преобразований, из S к T или из T к S, за исключением пользовательских.

Дополнительные сведения об ограничениях, накладываемых на пользовательские преобразования, см. в §10.10.3.

6.4.2 Операторы преобразования с нулификацией

Для заданного пользовательского оператора преобразования из необнуляемого типа значений S к необнуляемому типу значений T существует оператор преобразования с нулификацией, который используется для преобразования из S? к T?. Такой оператор преобразования с нулификацией осуществляет развертывание из S? в S, затем пользовательское преобразование из S к T, и затем свертывание из T в T? (за исключением случаев, когда существует прямое преобразование S? со значением null к T? со значением null.

Оператор преобразования с нулификацией классифицируется точно так же (явным или неявным), как и соответствующий ему базовый пользовательский оператор преобразования. Термин «пользовательское преобразование» применяется как к пользовательским операторам преобразования, так и к операторам с нулификацией.

6.4.3 Вычисление пользовательских преобразований

Пользовательское преобразование предназначено для преобразования значения из собственного исходного типа к другому, конечному типу. Вычисление пользовательского преобразования основывается на поиске пользовательского оператора преобразования, наиболее подходящего для заданных исходного и конечного типов. Поиск оператора осуществляется в несколько этапов:

· Поиск набора классов и структур, на основании которых будут рассматриваться пользовательские операторы преобразования. Этот набор включает в себя исходный и конечный типы, а также их базовые классы (при этом явно подразумевается, что пользовательские операторы могут быть объявлены только в классах и структурах, и типы, отличные от типов класса, не могут иметь базовых классов). На этом этапе, если исходный или конечный тип является обнуляемым_типом, вместо него используется базовый тип.

· На основании этого набора типов определяется, какие пользовательские операторы и операторы преобразования с нулификацией могут быть применены. Оператор преобразования применим только в том случае, если можно выполнить стандартное преобразование (§6.3) из исходного типа к типу операнда оператора, а также стандартное преобразование из типа результата оператора к конечному типу.

· На основании набора применимых пользовательских операторов определяется, какой из операторов является однозначно наиболее подходящим. В общих чертах, наиболее подходящим оператором является тот, для которого тип операнда наиболее «близок» к исходному типу, а тип результата — к конечному типу. Пользовательские операторы преобразования являются более предпочтительными по сравнению с операторами преобразования с нулификацией. Точные правила определения наиболее подходящего пользовательского оператора преобразования описываются в последующих разделах.

После определения наиболее подходящего пользовательского оператора преобразования осуществляется фактическое выполнение пользовательского преобразования, включающее в себя три этапа:

· Сначала, если это необходимо, выполняется стандартное преобразование из исходного типа к типу операнда для пользовательского оператора преобразования или оператора с нулификацией.

· Затем вызывается пользовательский оператор преобразования или оператор с нулификацией и выполняется преобразование.

· В завершение, если это необходимо, выполняется стандартное преобразование из типа результата для пользовательского оператора преобразования или оператора с нулификацией к конечному типу.

При вычислении пользовательского преобразования никогда не используется более одного пользовательского оператора или оператора с нулификацией. Другими словами, при преобразовании из типа S к типу T никогда не выполняется промежуточное преобразование из типа S к X с последующим преобразованием из X к T.

Точные определения вычисления явных и неявных пользовательских преобразований приведены в последующих разделах. В определениях используются следующие термины:

· Если существует стандартное неявное преобразование (§6.3.1) из типа A к типу B, и при этом типы A и B не являются типами_интерфейса, полагается, что тип A включен в тип B, а тип B включает тип A.

· Включающим типом верхнего уровня в наборе типов является тип, который включает все остальные типы набора. Если ни один тип не включает все остальные типы, включающий тип верхнего уровня в наборе отсутствует. Другими словами, включающий тип верхнего уровня — это «самый широкий» тип в наборе или тип, в который может быть неявно преобразован любой другой тип набора.

· Включаемым типом нижнего уровня в наборе типов является тип, который включается всеми остальными типами набора. Если ни один тип не включается всеми остальными типами, включаемый тип нижнего уровня в наборе отсутствует. Другими словами, включаемый тип нижнего уровня — это «самый узкий» тип в наборе или тип, который может быть неявно преобразован в любой другой тип набора.

6.4.4 Пользовательские неявные преобразования

Неявное пользовательское преобразование из типа S к типу T выполняется в следующем порядке:

· Определение типов S0 и T0. Если типы S или T являются обнуляемыми, типы S0 и T0 являются базовыми для них (в противном случае S0 и T0 считаются равными S и T соответственно).

· Поиск набора типов (D), на основании которого будут рассматриваться пользовательские операторы преобразования. Этот набор включает в себя тип S0 (если S0 является классом или структурой), базовые классы для S0 (если S0 является классом) и тип T0 (если T0 является классом или структурой).

· Поиск набора применимых пользовательских операторов и операторов преобразования с нулификацией (U). Этот набор включает в себя пользовательские операторы и операторы неявного преобразования с нулификацией, объявленные в классах и структурах набора D и осуществляющие преобразование из типа, включающего S, к типу, включаемому типом T. Если набор U пуст, преобразование считается неопределенным и порождает ошибку времени компиляции.

· Поиск наиболее подходящего исходного типа (SX) для операторов в наборе U:

o Если любой из операторов набора U осуществляет преобразование из S, в качестве типа SX выбирается S.

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

· Поиск наиболее подходящего конечного типа (TX) для операторов в наборе U:

o Если любой из операторов набора U осуществляет преобразование к T, в качестве типа TX выбирается T.

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

· Поиск наиболее подходящего оператора преобразования:

o Если набор U содержит только один пользовательский оператор преобразования из типа SX к TX, он является наиболее подходящим оператором преобразования.

o В противном случае, если набор U содержит только один оператор преобразования с нулификацией из типа SX к TX, он является наиболее подходящим оператором преобразования.

o В противном случае, преобразование считается неоднозначным и порождает ошибку времени компиляции.

· Применение преобразования:

o Если тип S не является типом SX, выполняется стандартное неявное преобразование из S к SX.

o Для преобразования из SX к TX вызывается наиболее подходящий оператор преобразования.

o Если тип TX не является типом T, выполняется стандартное неявное преобразование из TX к T.

6.4.5 Пользовательские явные преобразования

Явное пользовательское преобразование из типа S к типу T выполняется в следующем порядке:

· Определение типов S0 и T0. Если типы S или T являются обнуляемыми, типы S0 и T0 являются базовыми для них (в противном случае S0 и T0 считаются равными S и T соответственно).

· Поиск набора типов (D), на основании которого будут рассматриваться пользовательские операторы преобразования. Этот набор включает в себя тип S0 (если S0 является классом или структурой), базовые классы для S0 (если S0 является классом), тип T0 (если T0 является классом или структурой) и базовые классы для T0 (если T0 является классом).

· Поиск набора применимых пользовательских операторов и операторов преобразования с нулификацией (U). Этот набор включает в себя пользовательские операторы и операторы явного преобразования с нулификацией, объявленные в классах и структурах набора D и осуществляющие преобразование из типа, включающего S или включаемого им, к типу, включаемому типом T или включающему его. Если набор U пуст, преобразование считается неопределенным и порождает ошибку времени компиляции.

· Поиск наиболее подходящего исходного типа (SX) для операторов в наборе U:

o Если любой из операторов набора U осуществляет преобразование из S, в качестве типа SX выбирается S.

o В противном случае, если любой из операторов набора U осуществляет преобразование из типа, который включает тип S, то тип SX является включаемым типом нижнего уровня в объединенном наборе исходных типов для этих операторов. Если не найден включаемый тип нижнего уровня, преобразование считается неоднозначным и порождает ошибку времени компиляции.

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

· Поиск наиболее подходящего конечного типа (TX) для операторов в наборе U:

o Если любой из операторов набора U осуществляет преобразование к T, в качестве типа TX выбирается T.

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

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

· Поиск наиболее подходящего оператора преобразования:

o Если набор U содержит только один пользовательский оператор преобразования из типа SX к TX, он является наиболее подходящим оператором преобразования.

o В противном случае, если набор U содержит только один оператор преобразования с нулификацией из типа SX к TX, он является наиболее подходящим оператором преобразования.

o В противном случае преобразование считается неоднозначным и порождает ошибку времени компиляции.

· Применение преобразования:

o Если тип S не является типом SX, выполняется стандартное явное преобразование из S к SX.

o Для преобразования из SX к TX вызывается наиболее подходящий пользовательский оператор преобразования.

o Если тип TX не является типом T, выполняется стандартное явное преобразование из TX к T.

6.5 Преобразования анонимных функций

Выражение_анонимного_метода или лямбда_выражение классифицируется как анонимная функция (§7.14). Такое выражение не имеет типа, однако может быть неявно преобразовано к совместимому типу делегата или дерева выражений. В частности, тип делегата D совместим с анонимной функцией F при соблюдении следующих условий:

· Если функция F содержит подпись_анонимной_функции, тип D и функция F имеют одинаковое число параметров.

· Если функция F не содержит подписи_анонимной_функции, тип D может не иметь параметров или иметь несколько параметров любого типа, поскольку ни для какого параметра D не существует модификатора параметра out.

· Если функция F содержит список явно введенных параметров, каждый параметр типа D имеет такие же тип и модификаторы, как и соответствующий параметр функции F.

· Если функция F содержит список неявно введенных параметров, тип D не имеет параметров ref или out.

· Если тип D имеет тип возвращаемого значения void, а тело функции F представляет собой выражение, каждый параметр которого имеет тип соответствующего параметра D, тело функции F считается допустимым выражением (wrt §7), которое разрешено как выражение_оператора (§8.6).

· Если тип D имеет тип возвращаемого значения void, а тело функции F представляет собой блок оператора, каждый параметр которого имеет тип соответствующего параметра D, тело функции F считается допустимым блоком оператора (wrt §8.2), в котором никакой оператор return не задает выражения.

· Если тип D имеет отличный от void тип возвращаемого значения, а тело функции F представляет собой выражение, каждый параметр которого имеет тип соответствующего параметра D, тело функции F считается допустимым выражением (wrt §7), которое может быть неявно преобразовано к типу возвращаемого значения D.

· Если тип D имеет отличный от void тип возвращаемого значения, а тело функции F представляет собой блок оператора, каждый параметр которого имеет тип соответствующего параметра D, тело функции F считается допустимым блоком оператора (wrt §8.2) с недостижимой конечной точкой, где каждый оператор return задает выражение, которое может быть неявно преобразовано к типу возвращаемого значения D.

Тип дерева выражений Expression<D> совместим с анонимной функцией F в том случае, если тип делегата D совместим с F.

В приведенных ниже примерах используется универсальный тип делегата Func<A,R>, который представляет функцию, принимающую аргумент типа A и возвращающую значение типа R:

delegate R Func<A,R>(A arg);

В операциях присваивания

Func<int,int> f1 = x => x + 1; // Допустимо

Func<int,double> f2 = x => x + 1; // Допустимо

Func<double,int> f3 = x => x + 1; // Ошибка

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

В результате первой операции присваивания успешно выполняется преобразование анонимной функции к типу делегата Func<int,int>. Поскольку x имеет заданный тип int, x + 1 является допустимым выражением, которое может быть неявно преобразовано к типу int.

Аналогично, в результате второй операции присваивания успешно выполняется преобразование анонимной функции к типу делегата Func<int,double>, поскольку результат выражения x + 1 (тип int) может быть неявно преобразован к типу double.

Однако в результате третьей операции присваивания порождается ошибка времени компиляции. Поскольку x имеет заданный тип double, результат выражения x + 1 (тип double) не может быть неявно преобразован к типу int.

Анонимные функции могут влиять на разрешение перегрузки, а также использоваться при определении типа. Дополнительные сведения см. в §7.4.

6.5.1 Вычисление преобразования анонимной функции к типу делегата

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

Список вызова делегата, созданного на базе анонимной функции, содержит одну запись. Конечные объект и метод делегата не определены. В частности, не определено, является ли конечный объект делегата объектом null, а также значение this включающей функции-члена или любого другого объекта.

Допускаются (но не являются обязательными) преобразования семантически идентичных анонимных функций с одинаковым (возможно пустым) набором экземпляров внешних переменных к одному типу делегата. В результате таких преобразований возвращается один экземпляр делегата. Термин «семантически идентичные» означает, что выполнение анонимных функций с одинаковыми аргументами во всех случаях дает одинаковые результаты. Это правило позволяет оптимизировать код, аналогичный приведенному ниже.

delegate double Function(double x);

class Test
{
static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}

static void F(double[] a, double[] b) {
a = Apply(a, (double x) => Math.Sin(x));
b = Apply(b, (double y) => Math.Sin(y));
...
}
}

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

6.5.2 Вычисление преобразования анонимной функции к типу дерева выражений

В результате преобразования анонимной функции к типу дерева выражений создается дерево выражений (§4.6). Точнее говоря, в результате вычисления преобразования анонимной функции создается объектная структура, представляющая саму функцию. Точная структура дерева выражений, а также процесс его построения описываются в соответствующей документации.

6.5.3 Пример реализации

В данном разделе описывается возможная реализация преобразования анонимных функций в терминах других конструкций C#. Приведенная реализация базируется на принципах, используемых компилятором Microsoft C#, и не является ни обязательной, ни единственно возможной. Здесь приводится лишь краткое описание преобразований к дереву выражений, поскольку полная семантика таких преобразований в данной спецификации не рассматривается.

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

public delegate void D();

Анонимная функция простейшего вида не записывает внешние переменные:

class Test
{
static void F() {
D d = () => { Console.WriteLine("test"); };
}
}

Это можно преобразовать в код создания экземпляра делегата, ссылающегося на создаваемый компилятором статический метод, в который помещается код анонимной функции:

class Test
{
static void F() {
D d = new D(__Method1);
}

static void __Method1() {
Console.WriteLine("test");
}
}

В следующем примере анонимная функция ссылается на члены экземпляра this:

class Test
{
int x;

void F() {
D d = () => { Console.WriteLine(x); };
}
}

Это можно преобразовать в создаваемый компилятором метод экземпляра, содержащий код анонимной функции:

class Test
{
int x;

void F() {
D d = new D(__Method1);
}

void __Method1() {
Console.WriteLine(x);
}
}

В этом примере анонимная функция записывает локальную переменную:

class Test
{
void F() {
int y = 123;
D d = () => { Console.WriteLine(y); };
}
}

Время существования локальной переменной необходимо увеличить как минимум до времени существования делегата анонимной функции. Для этого можно поместить (поднять) локальную переменную в поле создаваемого компилятором класса. В этом случае создание экземпляра локальной переменной (§7.14.4.2) будет соответствовать созданию экземпляра класса компилятором. Обращение к локальной переменной будет соответствовать обращению к полю экземпляра создаваемого компилятором класса. Кроме того, анонимная функция становится методом экземпляра создаваемого компилятором класса:

class Test
{
void F() {
__Locals1 __locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}

class __Locals1
{
public int y;

public void __Method1() {
Console.WriteLine(y);
}
}
}

Следующая анонимная функция записывает this, а также две локальные переменные с различным временем существования:

class Test
{
int x;

void F() {
int y = 123;
for (int i = 0; i < 10; i++) {
int z = i * 2;
D d = () => { Console.WriteLine(x + y + z); };
}
}
}

Здесь компилятором создается класс для каждого блока оператора, в котором выполняется запись локальных переменных. За счет этого локальные переменные в каждом блоке имеют различное время существования. Экземпляр класса __Locals2, создаваемого компилятором для внутреннего блока оператора, включает в себя локальную переменную z и поле, содержащее ссылку на экземпляр __Locals1. Экземпляр класса __Locals1, создаваемого компилятором для внешнего блока оператора, включает в себя локальную переменную y и поле, содержащее ссылку на объект this включающей функции-члена. При такой структуре данных с помощью экземпляра __Local2 возможно обращение ко всем записанным внешним переменным. Таким образом, код анонимной функции реализуется как метод экземпляра этого класса.

class Test
{
void F() {
__Locals1 __locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++) {
__Locals2 __locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}

class __Locals1
{
public Test __this;
public int y;
}

class __Locals2
{
public __Locals1 __locals1;
public int z;

public void __Method1() {
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}

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

6.6 Преобразования группы методов

Существует неявное преобразование (§6.1) из группы методов (§7.1) к совместимому типу делегата. Для типа делегата D и выражения E, которое классифицируется как группа методов, неявное преобразование из E к D существует в том случае, если E содержит как минимум один метод, который применим в нормальной форме (§7.4.3.1) к списку аргументов, созданному с использованием типов параметров и модификаторов типа D, как описано далее.

Применение во время компиляции преобразования из группы методов E к типу делегата D описывается далее. Обратите внимание, что существование неявного преобразования из E к D не гарантирует, что его выполнение во время компиляции будет завершено без ошибок.

· Отдельный метод M выбирается соответствующим вызову метода (§7.5.5.1) в форме E(A) со следующими модификациями:

o Список аргументов A представляет собой список выражений, которые классифицируются как переменные с типом и модификатором (ref или out), соответствующими параметрам в списке_формальных_параметров типа D.

o Допустимыми считаются только те методы, которые применимы в нормальной форме (§7.4.3.1), а не те, которые применимы только в расширенной форме.

· Если алгоритм §7.5.5.1 порождает ошибку, возникает ошибка времени компиляции. В противном случае алгоритм возвращает наиболее подходящий метод M, имеющий столько же параметров, что и тип D. В этом случае преобразование считается существующим.

· Выбранный метод M должен быть совместим (§15.2) с типом делегата D. В противном случае порождается ошибка времени компиляции.

· Если выбранный метод M является методом экземпляра, выражение экземпляра, связанное с E, определяет конечный объект делегата.

· Если выбранный метод M является методом расширения, который определяется посредством обращения к члену выражения экземпляра, такое выражение определяет конечный объект делегата.

· Результатом преобразования является значение типа D, то есть вновь созданный делегат, который ссылается на выбранный метод и конечный объект.

Обратите внимание, что в результате этого процесса может быть создан делегат метода расширения. Это происходит в том случае, если алгоритм §7.5.5.1 не находит метод экземпляра, однако успешно выполняет вызов E(A) в качестве вызова метода расширения (§7.5.5.2). Созданный таким образом делегат записывает метод расширения и его первый аргумент.

Далее приведен пример преобразования группы методов:

delegate string D1(object o);

delegate object D2(string s);

delegate object D3();

delegate string D4(object o, params object[] a);

delegate string D5(int i);

class Test
{
static string F(object o) {...}

static void G() {
D1 d1 = F; // Допустимо
D2 d2 = F; // Допустимо
D3 d3 = F; // Ошибка — неприменимо
D4 d4 = F; // Ошибка — неприменимо в нормальной форме
D5 d5 = F; // Ошибка — применимо, но не совместимо

}
}

При присваивании d1 выполняется неявное преобразование группы методов F к значению типа D1.

Операция присваивания d2 демонстрирует возможность создания делегата для метода, который обладает производными типами параметра более низкого уровня (контравариантными) и производным типом возвращаемого значения более высокого уровня (ковариантным).

Операция присваивания d3 демонстрирует отсутствие преобразования в случае неприменимости метода.

Операция присваивания d4 демонстрирует, как необходимо применять метод в нормальной форме.

Операция присваивания d5 демонстрирует, что применение различного типа параметров и возвращаемых значений делегата и метода допустимо только для ссылочных типов.

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

object obj = new EventHandler(myDialog.OkClick);

может быть записан как

object obj = (EventHandler)myDialog.OkClick;

Группы методов могут влиять на разрешение перегрузки, а также использоваться при определении типа. Дополнительные сведения см. в §7.4.

Вычисление преобразования группы методов во время выполнения осуществляется в следующем порядке:

· Если выбранный во время компиляции метод представляет собой метод экземпляра или метод расширения, обращение к которому происходит как к методу экземпляра, конечный объект делегата определяется с помощью связанного с E выражения экземпляра:

o Выполняется вычисление выражения экземпляра. Если при вычислении порождается исключение, дальнейшие действия не выполняются.

o Если выражение экземпляра имеет ссылочный_тип, значение, возвращенное выражением экземпляра, становится конечным объектом. Если конечный объект является объектом null, порождается исключение System.NullReferenceException. Дальнейшие действия не выполняются.

o Если выражение экземпляра имеет тип_значений, выполняется операция упаковки (§4.3.1) для преобразования значения к объекту. Этот объект становится конечным объектом.

· В противном случае выбранный метод является частью вызова статического метода, а конечным объектом делегата является объект null.

· Выполняется выделение памяти под новый экземпляр типа делегата D. Если не удается выделить достаточный объем памяти для нового экземпляра, порождается исключение System.OutOfMemoryException. Дальнейшие действия не предпринимаются.

· Инициализируется новый экземпляр делегата, ссылающийся на метод, который был определен во время компиляции, а также на вычисленный выше конечный объект.

 


7. Выражения

Выражение — это последовательность операторов и операндов. В этой главе описывается синтаксис, порядок вычисления операндов и операторов, а также значение выражений.

7.1 Классы выражений

Выражение может иметь один из следующих классов.

· Значение. У каждого значения есть связанный с ним тип.

· Переменная. У каждой переменной есть связанный с ней тип, а именно объявленный тип переменной.

· Пространство имен. Выражение такого класса может находиться только в левой части доступа_к_члену (§7.5.4). В любом другом контексте выражение класса пространства имен вызывает ошибку времени компиляции.

· Тип. Выражение такого класса может находиться только в левой части доступа_к_члену (§7.5.4) или быть операндом оператора as (§7.9.11), is (§7.9.10) или typeof (§7.5.11). В любом другом контексте выражение класса типа вызывает ошибку времени компиляции.

· Группа методов, которая представляет собой набор перегруженных методов, получающихся в результате поиска члена (§7.3). У группы методов может быть связанное выражение экземпляра и связанный список аргументов типа. При вызове метода экземпляра результат вычисления выражения экземпляра становится экземпляром, который представляется оператором this (§7.5.7). Группа методов может быть частью выражения_вызова (§7.5.5) или выражения_создания_делегата (§7.5.10.5) и может неявно преобразовываться в совместимый тип делегата (§6.6). В любом другом контексте выражение с классом группы методов вызывает ошибку времени компиляции.

· Литерал null. Выражение такого класса может неявно преобразовываться в ссылочный тип или обнуляемый тип.

· Анонимная функция. Выражение такого класса может неявно преобразовываться в совместимый тип делегата или тип дерева выражения.

· Доступ к свойству. У каждого доступа к свойству есть связанный тип, а именно тип свойства. Кроме того, у доступа к свойству может быть связанное выражение экземпляра. При вызове метода доступа (блок get или set) в доступе к свойству экземпляра результат вычисления выражения экземпляра становится экземпляром, который представляется оператором this (§7.5.7).

· Доступ к событию. У каждого доступа к событию есть связанный тип, а именно тип события. Кроме того, у доступа к событию может быть связанное выражение экземпляра. Доступ к событию может быть левым операндом операторов += и -= (§7.16.3). В любом другом контексте выражение с классом доступа к событию вызывает ошибку времени компиляции.

· Доступ к индексатору. У каждого доступа к индексатору есть связанный тип, а именно тип индексатора. Кроме того, у доступа к индексатору есть связанное выражение экземпляра и связанный список аргументов. При вызове метода доступа (блок get или set) в доступе к индексатору результат вычисления выражения экземпляра становится экземпляром, который представляется оператором this (§7.5.7), а результат вычисления списка аргументов становится списком параметров вызова.

· Класс отсутствует. Это происходит, когда выражение является вызовом метода с типом возвращаемого значения void. Выражение без класса допустимо только в контексте выражения_оператора (§8.6).

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

Доступ к свойству или доступ к индексатору всегда изменяет свой класс на значение при вызове метода доступа get или set. Конкретный метод доступа определяется контекстом свойства или доступа к индексатору. Если доступ используется при присваивании, то для присваивания нового значения вызывается метод_доступа_set (§7.16.1). В противном случае для получения текущего значения вызывается метод доступа get (§7.1.1).

7.1.1 Значения выражений

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

· Значение переменной — это просто значение, которое хранится в данный момент в расположении, указанном переменной. Для получения значения переменной оно должно быть явно присвоено (§5.3), в противном случае возникает ошибка времени компиляции.

· Значение выражения доступа к свойству получается путем вызова метода_доступа_get для свойства. Если у свойства нет метода_доступа_get, возникает ошибка времени компиляции. В противном случае выполняется вызов функции-члена (§7.4.4), и значением выражения доступа к свойству становится результат этого вызова.

· Значение выражения доступа к индексатору получается путем вызова метода_доступа_get для индексатора. Если у индексатора нет метода_доступа_get, возникает ошибка времени компиляции. В противном случае выполняется вызов функции-члена (§7.4.4) со списком аргументов, связанных с выражением доступа к индексатору, и значением выражения доступа к индексатору становится результат этого вызова.

7.2 Операторы

Выражения состоят из операндов и операторов. Операторы в выражении указывают, какие операции производятся с операндами. К операторам относятся, например, +, -, *, / и new. К операндам относятся, например, литералы, поля, локальные переменные и выражения.

Существует три типа операторов.

· Унарные операторы. У унарного оператора есть только один операнд и оператор может записываться в префиксной форме (например, –x) или постфиксной форме (например, x++).

· Бинарные операторы. У бинарных операторов два операнда и они записываются в виде инфикса (например, x + y).

· Тернарный оператор. Существует только один тернарный оператор, ?:. В нем три операнда и используется инфиксная запись (c? x: y).

Порядок вычисления операторов в выражении определяется приоритетом и ассоциативностью операторов (§7.2.1).

Операнды в выражении вычисляются слева направо. Например, в выражении F(i) + G(i++) * H(i) вызывается метод F со старым значением i, затем вызывается метод G со старым значением i и, наконец, вызывается метод H с новым значением i. Это никак не связано с приоритетом операторов.

Некоторые операторы допускают перегрузку. Перегрузка операторов позволяет использовать пользовательскую реализацию операторов в операциях, в которых один или оба операнда имеют пользовательский тип класса или структуры (§7.2.2).

7.2.1 Приоритет и ассоциативность операторов

Когда выражение содержит несколько операторов, порядок вычисления отдельных операторов задается приоритетом операторов. Например, выражение x + y * z вычисляется как x + (y * z), поскольку приоритет оператора * выше, чем у бинарного оператора +. Приоритет оператора задается в определении связанной с ним грамматической структуры. Например, аддитивное_выражение состоит из последовательности мультипликативных_выражений, разделенных операторами + или –, таким образом, операторы + или – имеют более низкий приоритет, чем операторы *, / и %.

В следующей таблице приводятся все операторы в соответствии с их приоритетом от самого высокого до самого низкого.

 

Раздела Категория Операторы
7.5 Основной x.y f(x) a[x] x++ x-- new typeof default checked unchecked delegate
7.6 Унарный + - ! ~ ++x --x (T)x
7.7 Мультипликативный * / %
7.7 Аддитивный + -
7.8 Сдвиг << >>
7.9 Отношение и проверка типа < > <= >= is as
7.9 Равенство == !=
7.10 Логическое AND &
7.10 Исключающее XOR ^
7.10 Логическое OR |
7.11 Условное AND &&
7.11 Условное OR ||
7.12 Объединение с нулем ??
7.13 Условный ?:
7.16, 7.14 Присваивание и лямбда-выражение = *= /= %= += -= <<= >>= &= ^= |= =>

 

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

· За исключением операторов присваивания все бинарные операторы обладают левой ассоциативностью. Это означает, что все операции выполняются слева направо. Например, выражение x + y + z вычисляется как (x + y) + z.

· Операторы присваивания и условный оператор (?:) обладают правой ассоциативностью. Это означает, что все операции выполняются справа налево. Например, выражение x = y = z вычисляется как x = (y = z).

Приоритетом и ассоциативностью можно управлять с помощью скобок. Например, в выражении x + y * z сначала y умножается на z, а затем результат складывается с x, но в выражении (x + y) * z сначала складываются x и y, а затем результат умножается на z.

7.2.2 Перегрузка операторов

У всех унарных и бинарных операторов есть стандартная реализация, доступная автоматически в любом выражении. В дополнение к стандартным реализациям объявление operator в классах и структурах позволяет использовать пользовательские реализации (§10.10). Пользовательские реализации операторов всегда имеют больший приоритет по сравнению со стандартными реализациями операторов. Как описывается в разделах §7.2.3 и §7.2.4, стандартные реализации операторов рассматриваются только тогда, когда не найдено применимых пользовательских реализаций.

К унарным операторам, допускающим перегрузку, относятся:

+ - ! ~ ++ -- true false

Несмотря на то что true и false явно в выражениях не используются (и поэтому не включены в таблицу с приоритетами в разделе §7.2.1), они считаются операторами, потому что вызываются в некоторых контекстах выражений: логических выражений (§7.19) и выражений с условными (§7.13) и условными логическими операторами (§7.11).

К бинарным операторам, допускающим перегрузку, относятся:

+ - * / % & | ^ << >> == != > < >= <=

Перегрузка возможна только для операторов, указанных выше. В частности, невозможна перегрузка операторов доступа к членам, вызова методов или операторов =, &&, ||, ??, ?:, =>, checked, unchecked, new, typeof, default, as и is.

При перегрузке бинарного оператора связанный оператор присваивания (если он есть) также неявно перегружается. Например, при перегрузке оператора * также выполняется перегрузка оператора *=. Это описывается дальше в разделе §7.16.2. Обратите внимание, что сам оператор присваивания (=) нельзя перегрузить. При присваивании всегда происходит простое побитовое копирование значения в переменную.

Перегрузка операций приведения типов, например (T)x, осуществляется путем предоставления пользовательских преобразований (§6.4).

Доступ к элементу, например a[x], не считается оператором, допускающим перегрузку. Вместо этого поддерживается пользовательская индексация в индексаторах (§10.9).

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

 

Запись в виде оператора Запись в виде функции
op x operator op(x)
x op operator op(x)
x op y operator op(x, y)

 

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

В объявлениях пользовательских операторов нельзя изменять синтаксис, приоритет и ассоциативность оператора. Например, оператор / всегда будет бинарным оператором, иметь приоритет, указанный в разделе 0 и всегда будет обладать левой ассоциативностью.

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

В описании отдельных операторов в разделах §7.5-§7.11 указаны стандартные реализации и дополнительные правила, применимые к каждому оператору. В этих описаниях используются термины разрешение перегрузки унарных операторов, разрешение перегрузки бинарных операторов и числовое расширение, определения которых находятся в дальнейших разделах.

7.2.3 Разрешение перегрузки унарных операторов

Операция вида op x или x op, где op — это унарный оператор, допускающий перегрузку, а x — это выражение типа X, выполняется следующим образом.

· С помощью правил из раздела §7.2.5 определяется набор пользовательских операторов-кандидатов, предоставленных в типе X для операции operator op(x).

· Если набор пользовательских операторов-кандидатов не пустой, то он становится набором операторов-кандидатов для операции. В противном случае набором операторов-кандидатов для операции становятся стандартные реализации унарного оператора operator op, включая их варианты с нулификацией. Стандартные реализации данного оператора указываются в описании оператора (§7.5 и §7.6).

· Чтобы выбрать оператор, подходящий больше всего в соответствии со списком аргументов (x), к набору операторов-кандидатов применяются правила разрешения перегрузки из раздела §7.4.3, и этот оператор становится результатом процесса разрешения перегрузки. Если при разрешении перегрузки не удалось выбрать один подходящий оператор, то возникает ошибка времени компиляции.

7.2.4 Разрешение перегрузки бинарных операторов

Операция вида x op y, где op — это бинарный оператор, допускающий перегрузку, x — это выражение типа X, а y — это выражение типа Y, выполняется следующим образом.

· Определяется набор пользовательских операторов-кандидатов, предоставленных в типах X и Y для операции operator op(x, y). Этот набор представляет собой объединение операторов-кандидатов, предоставленных в типе X, и операторов-кандидатов, предоставленных в типе Y. Каждый из этих составляющих наборов определяется в соответствии с правилами из раздела §7.2.5. Если X и Y представляют один тип или X и Y являются производными от общего базового типа, тогда в объединенном наборе общие операторы-кандидаты появляются только один раз.

· Если набор пользовательских операторов-кандидатов не пустой, то он становится набором операторов-кандидатов для операции. В противном случае набором операторов-кандидатов для операции становятся стандартные реализации бинарного оператора operator op, включая их варианты с нулификацией. Стандартные реализации данного оператора указываются в описании оператора (§7.7-§7.11).

· Чтобы выбрать оператор, более всего подходящий для списка аргументов (x, y), к набору операторов-кандидатов применяются правила разрешения перегрузки из раздела §7.4.3, и этот оператор становится результатом процесса разрешения перегрузки. Если при разрешении перегрузки не удалось выбрать один подходящий оператор, то возникает ошибка времени компиляции.

7.2.5 Пользовательские операторы-кандидаты

Если взять тип T и операцию operator op(A), где op — это оператор, допускающий перегрузку, а A — это список аргументов, то набор пользовательских операторов-кандидатов, предоставляемых типом T для оператора operator op(A), определяется следующим образом.

· Определяется тип T0. Если T допускает значения null, то T0 является его базовым типом, в противном случае T0 совпадает с T.

· Для всех объявлений operator op в T0 и всех вариантов таких операторов с нулификацией, если в соответствии со списком аргументов A применим хотя бы один оператор (§7.4.3.1), то набор операторов-кандидатов состоит из всех таких применимых операторов в T0.

· Иначе, если T0 имеет тип object, то набор операторов-кандидатов является пустым.

· Иначе набор операторов-кандидатов, предоставленных в T0, является набором операторов-кандидатов, предоставленных в непосредственном базовом классе для T0 или в фактическом базовом классе для T0, если T0 является параметром типа.

7.2.6 Числовое расширение

Числовое расширение — это автоматическое проведение определенных неявных преобразований операндов в стандартных унарных и бинарных числовых операторах. Числовое расширение — это не отдельный механизм, а скорее эффект от проведения разрешения перегрузки для стандартных операторов. Числовое расширение специально не влияет на вычисление пользовательских операторов, хотя пользовательские операторы можно реализовать для получения похожих эффектов.

В качестве примера числового расширения можно рассмотреть стандартные реализации бинарного оператора *:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

При применении правил разрешения перегрузки (§7.4.3) к этому набору операторов результатом является первый оператор, для которого существуют неявные преобразования операндов. Например, для операции b * s, где b имеет тип byte, а s — тип short, при разрешении перегрузки будет выбрано operator *(int, int). Таким образом, b и s преобразуются в тип int, и результат имеет тип int. Точно так же для операции i * d, где i имеет тип int, а d — тип double, при разрешении перегрузки будет выбрано operator *(double, double).

7.2.6.1 Числовое расширение унарных операторов

Числовое расширение унарных операторов выполняется для операндов стандартных унарных операторов +, – и ~. Числовое расширение унарных операторов заключается в простом преобразовании операндов типа sbyte, byte, short, ushort или char в тип int. Кроме того, для унарного оператора – при числовом расширении унарного оператора происходит преобразование операндов с типом uint в тип long.

7.2.6.2 Числовое расширение бинарных операторов

Числовое расширение бинарных операторов выполняется для операндов стандартных бинарных операторов +, –, *, /, %, &, |, ^, ==, !=, >, <, >= и <=. При числовом расширении бинарных операторов оба операнда преобразуются в общий тип. Для операторов, отличных от операторов отношения, этот тип становится типом результата операции. Числовое расширение бинарных операторов состоит в применении следующих правил в указанном здесь порядке.

· Если один из операндов имеет тип decimal, то другой операнд преобразуется в тип decimal, либо если другой операнд имеет тип float или double, то возникает ошибка времени компиляции.

· Иначе, если один из операндов имеет тип double, то другой операнд преобразуется в тип double.

· Иначе, если один из операндов имеет тип float, то другой операнд преобразуется в тип float.

· Иначе, если один из операндов имеет тип ulong, то другой операнд преобразуется в тип ulong, либо если другой операнд имеет тип sbyte, short, int или long, то возникает ошибка времени компиляции.

· Иначе, если один из операндов имеет тип long, то другой операнд преобразуется в тип long.

· Иначе, если один из операндов имеет тип uint, а другой операнд имеет тип sbyte, short или int, то оба операнда преобразуются в тип long.

· Иначе, если один из операндов имеет тип uint, то другой операнд преобразуется в тип uint.

· Иначе оба операнда преобразуются в тип int.

Обратите внимание, что первое правило запрещает любые операции, в которых смешиваются тип decimal и типы double или float. Это правило вытекает из того факта, что неявное преобразование между типом decimal и типом double или float не существует.

Также обратите внимание, что операнд не может иметь тип ulong, когда другой операнд имеет целый тип со знаком. Это так, поскольку не существует целого типа, который может представлять полный спектр значений ulong вместе с целыми типами со знаком.

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

В данном примере

decimal AddPercent(decimal x, double percent) {
return x * (1.0 + percent / 100.0);
}

возникает ошибка времени компиляции, поскольку значения типа decimal нельзя умножать на значение типа double. Эта ошибка устраняется с помощью явного преобразования второго операнда в тип decimal, ср.:

decimal AddPercent(decimal x, double percent) {
return x * (decimal)(1.0 + percent / 100.0);
}

7.2.7 Операторы с нулификацией

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

· Для унарных операторов

+ ++ - -- ! ~

вариант оператора с нулификацией существует, если типы операнда и результата оба являются необнуляемыми типами значений. Вариант оператора с нулификацией создается с помощью добавления одного модификатора ? к типу операнда и результата. Значением оператора с нулификацией является значение null, если операнд равен null. В противном случае в операторе с нулификацией с операнда снимается упаковка, применяется базовый оператор и затем создается упаковка для результата.

· Для бинарных операторов.

+ - * / % & | ^ << >>

вариант оператора с нулификацией существует, если типы операнда и результата являются необнуляемыми типами значений. Вариант оператора с нулификацией создается с помощью добавления одного модификатора ? к типу каждого операнда и результата. Значением оператора с нулификацией является значение null, если один или оба операнда равны null (исключение составляют операторы & и | типа bool?, см. раздел §7.10.3). В противном случае в операторе с нулификацией с операндов снимается упаковка, применяется базовый оператор и затем создается упаковка для результата.

· Для операторов равенства

== !=

вариант оператора с нулификацией существует, если оба операнда имеют необнуляемый тип значений, а результат имеет тип bool. Вариант оператора с нулификацией создается с помощью добавления одного модификатора ? к каждому типу операнда. В операторе с нулификацией равными считаются два значения null, а неравными — значение null и любое другое ненулевое значение. Если оба операнда не равны null, то в операторе с нулификацией с операндов снимается упаковка и для получения результата с типом bool применяется базовый оператор.

· Для операторов отношения

< > <= >=

вариант оператора с нулификацией существует, если оба операнда имеют необнуляемый тип значений, а результат имеет тип bool. Вариант оператора с нулификацией создается с помощью добавления одного модификатора ? к каждому типу операнда. Значением оператора с нулификацией является false, если один или оба операнда имеют значение null. В противном случае в операторе с нулификацией с операндов снимается упаковка и для получения результата с типом bool применяется базовый оператор.

7.3 Поиск членов

Поиск членов — это процесс, при котором определяется значение имени в контексте данного типа. Поиск членов может выполняться в составе процесса оценки простого_имени (§7.5.2) или доступа_к_члену (§7.5.4) в выражении. Если простое_имя или доступ_к_члену указывается в виде простого_выражения для выражения_вызова (§7.5.5.1), то говорят, что этот член вызываемый.

Если член является методом или событием или если он является константой, полем или свойством типа делегата (§15), то говорят, что этот метод допускает вызов.

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

Поиск члена с именем N и K параметрами типа в типе T происходит следующим образом.

· Сначала определяется набор доступных членов с именем N.

o Если T — это параметр типа, то такой набор представляет собой объединение наборов доступных членов с именем N из каждого типа, указанного в качестве первичного или вторичного ограничения (§10.1.5) для T, вместе с набором доступных членов с именем N в object.

o В противном случае набор состоит из всех доступных членов (§3.5) с именем N в типе T, включая унаследованные члены и доступные члены с именем N в типе object. Если T — это сформированный тип, то набор членов получается заменой аргументов типа, как описано разделе в §10.3.2. Из набора исключаются члены с модификатором override.

· Затем, если K равно нулю, удаляются все вложенные типы, в объявления которых входят параметры типа. Если K не равно нулю, удаляются все члены с отличающимся числом параметров типа. Обратите внимание, что когда K равно нулю, методы с параметрами типа не удаляются, поскольку в процессе определения типа (§7.4.2) могут выводиться аргументы типа.

· Затем, если метод является вызываемым, из набора удаляются все не_допускающие_вызов члены.

· Затем из набора удаляются члены, скрытые другими членами. Для каждого члена S.M в наборе, где S — это тип, в котором определен член M, применяются следующие правила.

o Если M является константой, полем, свойством, событием или элементом перечисления, из набора удаляются все члены, объявленные в базовом типе S.

o Если M является объявлением типа, из набора удаляются все объявленные в базовом типе S члены, не являющиеся типами, а также все объявления типов с таким же числом параметров типа, как и в M, объявленные в базовом типе S.

o Если M является методом, то из набора удаляются все объявленные в базовом типе S члены, не являющиеся методами.

· Затем из набора удаляются члены интерфейса, скрытые членами класса. Этот шаг выполняется, только если T является параметром типа и у T есть как действительный базовый класс, отличный от класса object, так и действительный непустой набор интерфейсов (§10.1.5). Для каждого члена S.M в наборе, где S — это тип, в котором определен член M, применяются следующие правила, если S является объявлением класса, отличного от object.

o Если M является константой, полем, свойством, событием, элементом перечисления или объявлением типа, из набора удаляются все члены, объявленные в объявлении интерфейса.

o Если M является методом, из набора удаляются все объявленные в объявлении интерфейса члены, не являющиеся методами, а также все объявленные в объявлении интерфейса методы с такой же подписью, как и у M.

· Наконец, после удаления скрытых членов определяется результат поиска.

o Если набор состоит из одного члена, который не является методом, то этот член является результатом поиска.

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

o Иначе поиск привел к неоднозначным результатам, что вызывает ошибку времени компиляции.

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

7.3.1 Базовые типы

Для поиска членов считается, что у типа T есть следующие базовые типы:

· Если T имеет тип object, то у него нет базового типа.

· Если T имеет тип_перечисления, то базовыми классами T являются типы классов System.Enum, System.ValueType и object.

· Если T имеет тип_структуры, то базовыми классами T являются типы классов System.ValueType и object.

· Если T имеет тип_класса, то базовыми классами T являются базовые классы T, включая тип класса object.

· Если T имеет тип_интерфейса, то базовыми классами T являются базовые интерфейсы T и тип класса object.

· Если T имеет тип_массива, то базовыми классами T являются типы классов System.Array и object.

· Если T имеет тип_делегата, то базовыми классами T являются типы классов System.Delegate и object.

7.4 Функции-члены

Функции-члены — это члены, содержащие исполняемые операторы. Функции-члены всегда являются членами типов и не могут быть членами пространств имен. В C# определены следующие категории функций-членов:

· Методы

· Свойства

· События

· Индексаторы

· Пользовательские операторы

· Конструкторы экземпляров

· Статические конструкторы

· Деструкторы

За исключением деструкторов и статических конструкторов (которые нельзя вызывать явно) операторы в функциях-членах выполняются при вызовах функции-члена. Фактический синтаксис вызова функции-члена зависит от конкретной категории функции-члена.

Список аргументов (§7.4.1) для функции-члена содержит фактические значения или ссылки на переменные для параметров функции-члена.

При вызове методов, индексаторов, операторов и конструкторов экземпляров выполняется разрешение перегрузки с целью определить, какой набор кандидатов функций-членов будет вызываться. Этот процесс описывается в разделе §7.4.3.

После определения конкретной функции-члена во время компиляции (возможно, в процессе разрешения перегрузки), фактический процесс вызова функции-члена во время выполнения описывается в разделе §7.4.4.

В следующей таблице приводится сводка по операциям, которые выполняются в конструкциях с шестью категориями функций-членов, допускающих явный вызов. В таблице e, x, y и value обозначают выражения с типом переменной или значения, T означает выражение типа, F — это простое имя метода, а P — это простое имя свойства.

 

Конструкция Пример Описание
Вызов метода F(x, y) Для выбора наиболее подходящего метода F в содержащем классе или структуре используется разрешение перегрузки. Метод вызывается со списком аргументов (x, y). Если метод не относится к типу static, то выражением экземпляра является this.
T.F(x, y) Для выбора наиболее подходящего метода F в классе или структуре T используется разрешение перегрузки. Если метод не относится к типу static, возникает ошибка времени компиляции. Метод вызывается со списком аргументов (x, y).
e.F(x, y) Для выбора наиболее подходящего метода F в классе, структуре или интерфейсе, передаваемом в типе e используется разрешение перегрузки. Если метод относится к типу static, возникает ошибка времени компиляции. Метод вызывается с выражением экземпляра e и со списком аргументов (x, y).
Доступ к свойству P В содержащем классе или структуре вызывается метод доступа get свойства P. Если свойство P доступно только для записи, то возникает ошибка времени компиляции. Если свойство не относится к типу static, то выражением экземпляра является this.
P = value В содержащем классе или структуре вызывается метод доступа set свойства P со списком аргументов (value). Если свойство P доступно только для чтения, то возникает ошибка времени компиляции. Если свойство не относится к типу static, то выражением экземпляра является this.
T.P В классе или структуре T вызывается метод доступа get свойства P. Если свойство P не относится к типу static или доступно только для записи, то возникает ошибка времени компиляции.
T.P = value В классе или структуре T вызывается метод доступа set свойства P со списком аргументов (value). Если свойство P не относится к типу static или доступно только для чтения, то возникает ошибка времени компиляции.
e.P В классе, структуре или интерфейсе, передаваемом по типу e, вызывается метод доступа get свойства P с выражением экземпляра e. Если свойство P относится к типу static или доступно только для записи, то возникает ошибка времени компиляции.
e.P = value В классе, структуре или интерфейсе, передаваемом по типу e, вызывается метод доступа set свойства P с выражением экземпляра e и списком аргументов (value). Если свойство P относится к типу static или доступно только для чтения, то возникает ошибка времени компиляции.
Доступ к событию E += value В содержащем классе или структуре вызывается метод доступа add события E. Если событие E не относится к типу static, то выражением экземпляра является this.
E -= value В содержащем классе или структуре вызывается метод доступа remove события E. Если событие E не относится к типу static, то выражением экземпляра является this.
T.E += value В классе или структуре T вызывается метод доступа add события E. Если событие E не относится к типу static, возникает ошибка времени компиляции.
T.E -= value В классе или структуре T вызывается метод доступа remove события E. Если событие E не относится к типу static, возникает ошибка времени компиляции.
e.E += value В классе, структуре или интерфейсе, передаваемом в типе e, вызывается метод доступа add события E с выражением экземпляра e. Если событие E относится к типу static, возникает ошибка времени компиляции.
e.E -= value В классе, структуре или интерфейсе, передаваемом в типе e, вызывается метод доступа remove события E с выражением экземпляра e. Если событие E относится к типу static, возникает ошибка времени компиляции.
Доступ к индексатору e[x, y] Для выбора наиболее подходящего индексатора в классе, структуре или интерфейсе, передаваемом в типе e, используется разрешение перегрузки. Вызывается метод доступа get индексатора с выражением экземпляра e и со списком аргументов (x, y). Если индексатор доступен только для записи, то возникает ошибка времени компиляции.
e[x, y] = value Для выбора наиболее подходящего индексатора в классе, структуре или интерфейсе, передаваемом в типе e, используется разрешение перегрузки. Вызывается метод доступа set индексатора с выражением экземпляра e и со списком аргументов (x, y, value). Если индексатор доступен только для чтения, то возникает ошибка времени компиляции.
Вызов оператора -x Для выбора наиболее подходящего унарного оператора в классе или структуре, передаваемой в типе x, используется разрешение перегрузки. Выбранный оператор вызывается со списком аргументов (x).
x + y Для выбора наиболее подходящего бинарного оператора в классах или структурах, передаваемых в типах x и y, используется разрешение перегрузки. Выбранный оператор вызывается со списком аргументов (x, y).
Вызов конструктора экземпляра new T(x, y) Для выбора наиболее подходящего конструктора экземпляра в классе или структуре T используется разрешение перегрузки. Конструктор экземпляра вызывается со списком аргументов (x, y).

 

7.4.1 Списки аргументов

Каждый вызов функции-члена и делегата включает список аргументов, в котором передаются фактические параметры или ссылки на переменные для параметров функции-члена. Синтаксис записи списка аргументов при вызове функции-члена зависит от категории функции-члена.

· Для конструкторов экземпляров, методов и делегатов аргументы указываются в виде списка_аргументов, как показано ниже.

· Для свойств список аргументов пуст при вызове метода доступа get и содержит выражение, указанное в качестве правого операнда оператора присваивания, при вызове метода доступа set.

· Для событий список аргументов состоит из выражения, указанного в качестве правого операнда оператора += или -=.

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

· В пользовательских операторах список аргументов состоит из одного операнда для унарного оператора и из двух операторов для бинарного оператора.

Аргументы свойств (§10.7), событий (§10.8) и пользовательских операторов (§10.10) всегда передаются как параметры значений (§10.6.1.1).

Аргументы индексаторов (§10.9) всегда передаются как параметры значений (§10.6.1.1) или массивы параметров (§10.6.1.4). Для этих категорий функций-членов параметры ссылки и параметры вывода не поддерживаются.

Аргументы при вызове конструктора экземпляра, метода или делегата указываются в виде списка_аргументов:

список аргументов:
аргумент
список аргументов , аргумент

аргумент
выражение
ref ссылка на переменную
out ссылка на переменную

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

· Выражение, указывающее, что аргумент передается в виде параметра значения (§10.6.1.1).

· Ключевое слово ref, за которым следует ссылка_на_переменную (§5.4), указывающее, что аргумент передается в виде параметра ссылки (§10.6.1.2). Чтобы переменную можно было передавать в виде параметра ссылки, она должна быть явно присвоена (§5.3). Ключевое слово out, за которым следует ссылка_на_переменную (§5.4), указывающее, что аргумент передается в виде параметра вывода (§10.6.1.3). После вызова функции-члена, в котором переменная передается в виде параметра вывода, переменная считается явно присвоенной (§5.3).

При обработке вызова функции-члена во время выполнения (§7.4.4) выражения или ссылки на переменные списка аргументов вычисляются слева направо в следующем порядке.

· Для параметра значения вычисляется выражение аргумента и выполняется неявное преобразование (§6.1) в соответствующий тип параметра. Результат становится исходным значением параметра значения при вызове функции-члена.

· Для параметра ссылки или вывода вычисляется ссылка на переменную и получившееся расположение в памяти становится расположением, которое представляется параметром при вызове функции-члена. Если ссылка на переменную, предоставленная в виде параметра ссылки или вывода, является элементом массива ссылочного_типа, во время выполнения проверяется, совпадает ли тип элемента массива с типом параметра. Если эта проверка не проходит, вызывается исключение System.ArrayTypeMismatchException.

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

· Когда функция-член с массивом параметров вызывается в нормальной форме, аргумент массива параметров должен быть одним выражением с типом, который можно неявно преобразовать (§6.1) в тип массива параметров. В этом случае использование массива параметров не отличается от использования параметра значения.

· Когда функция-член с массивом параметров вызывается в расширенной форме, при вызове необходимо указать любое число аргументов для массива параметров, где каждый аргумент представляет собой выражение с типом, который можно неявно преобразовать (§6.1) в тип элементов массива параметров. В этом случае при вызове создается экземпляр типа массива параметров с длиной, соответствующей числу аргументов, происходит инициализация элементов экземпляра массива с помощью указанных значений аргументов и в качестве фактического аргумента используется этот только что созданный экземпляр массива.

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

class Test
{
static void F(int x, int y, int z) {
System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z);
}

static void Main() {
int i = 0;
F(i++, i++, i++);
}
}

получается вывод

x = 0, y = 1, z = 2

По правилам ковариации массива (§12.50) значение массива типа A[] может быть ссылкой на экземпляр массива типа B[], если существует неявное преобразование ссылочного типа из B в A. Согласно этим правилам, когда элемент массива ссылочного_типа передается в качестве параметра ссылки или вывода, во время выполнения необходимо убедиться, что фактический тип элементов массива идентичен типу параметра. В данном примере

class Test
{
static void F(ref object x) {...}

static void Main() {
object[] a = new object[10];
object[] b = new string[10];
F(ref a[0]); // Ok
F(ref b[1]); // ArrayTypeMismatchException
}
}

при втором вызове F будет вызвано исключение System.ArrayTypeMismatchException, потому что фактическим типом b является string, а не object.

Когда функция-член с массивом параметров вызывается в расширенной форме, вызов обрабатывается точно так же, как если бы для расширенных параметров было указано выражение создания массива с инициализатором массива (§7.5.10.4). Например, при объявлении

void F(int x, int y, params object[] args);

следующие вызовы расширенной формы метода

F(10, 20);
F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

в точности соответствуют

F(10, 20, new object[] {});
F(10, 20, new object[] {30, 40});
F(10, 20, new object[] {1, "hello", 3.0});

В частности, обратите внимание, что когда в массиве параметров аргументы не передаются, создается пустой массив.

7.4.2 Вывод типа

При вызове универсального метода без указания аргументов типа процесс вывода типа пытается определить аргументы типа для вызова. Наличие вывода типа позволяет использовать более удобный синтаксис для вызова универсального метода и позволяет разработчику избегать указания избыточных данных о типе. Например, при объявлении метода

class Chooser
{
static Random rand = new Random();

public static T Choose<T>(T first, T second) {
return (rand.Next(2) == 0)? first: second;
}
}

можно вызвать метод Choose без явного указания аргумента типа:

int i = Chooser.Choose(5, 213); // вызывает Choose<int>

string s = Chooser.Choose("foo", "bar"); //вызывает Choose<string>

С помощью вывода типа аргументы типа int и string определяются из аргументов метода.

Вывод типа происходит при обработке вызова метода во время компиляции (§7.5.5.1) до этапа разрешения перегрузки для вызова. Когда в вызове метода указывается конкретная группа методов и не указываются аргументы типа, вывод типа применяется к каждому универсальному методу в группе методов. Если вывод типа завершается успехом, то полученные аргументы типа используются для определения типов аргументов для последующего разрешения перегрузки. Если при разрешении перегрузки выбирается вызов универсального метода, то при вызове в качестве фактических аргументов типа будут использоваться полученные аргументы. Если вывод типа для конкретного метода завершается неудачно, то такой метод не участвует в разрешении перегрузки. Ошибка при выводе типа сама по себе не приводит к ошибке времени компиляции. Однако она часто приводит к ошибке времени компиляции, когда при разрешении перегрузки не удается найти применимые методы.

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

Tr M<X1…Xn>(T1 x1 … Tm xm)

При вызове метода в виде M(E1 …Em) задачей вывода типа является найти уникальные аргументы типа S1…Sn для каждого параметра типа X1…Xn, чтобы вызов M<S1…Sn>(E1…Em) оказался действительным.

Во время процесса вывода каждый параметр типа Xi либо фиксирован с определенным типом Si, либо нефиксирован со связанным набором границ. Каждая граница представляет собой некоторый тип T. Изначально каждая переменная типа Xi является нефикисированной с пустым набором границ.

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

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

7.4.2.1 Первый этап

Для каждого аргумента метода Ei:

  • если Ei является анонимной функцией или группой методов, выполняется явный вывод типа параметра (§7.4.2.7) из Ei с типом Ti
  • в противном случае выполняется вывод типа вывода (§7.4.2.6) из Ei с типом Ti

7.4.2.2 Второй этап

Второй этап продолжается следующим образом.

  • Фиксируются все нефиксированные переменные типа Xi, которые не зависят (§7.4.2.5) от каких-либо Xj (§7.4.2.10).
  • Если таких переменных типа нет, то фиксируются все нефиксированные переменные типа Xi, для которых верно, что
    • существует по крайней мере одна переменная типа Xj, которая зависит от Xi;
    • у Xi имеется непустой набор границ.
  • Если таких переменных типа не существует и все еще остаются нефиксированные переменные типа, то вывод типа завершается сбоем.
  • Иначе, если нефиксированных переменных типа больше не существует, вывод типа завершается успешно.
  • Иначе для всех аргументов Ei с соответствующим типом параметра Ti, где типы вывода (§7.4.2.4) содержат нефиксированные переменные типа Xj, а типа ввода (§7.4.2.3) не содержат, выполняется вывод типа вывода (§7.4.2.6) для Ei с типом Ti. После этого второй этап повторяется.

7.4.2.3 Типы ввода

Если E является группой методов или анонимной функцией с неявным указанием типа, а T является типом делегата или типом дерева выражения, то все типы параметров T являются типами ввода E с типом T.

7.4.2.4 Типы вывода

Если E является группой методов или анонимной функцией, а T является типом делегата или типом дерева выражения, то типом возвращаемого значения T является тип вывода E с типом T.

7.4.2.5 Зависимость

Нефиксированная переменная типа Xi непосредственно зависит от нефиксированной переменной типа Xj, если для некоторого аргумента Ek с типом Tk Xj оказывается в типе ввода Ek с типом Tk, а Xi оказывается в типе вывода Ek с типом Tk.

Xj зависит от Xi, если Xj непосредственно зависит от Xi или если Xi непосредственно зависит от Xk и Xk зависит от Xj. Таким образом, отношение «зависит от» является транзитивным, но не является рефлексивным замыканием отношения «непосредственно зависит от».

7.4.2.6 Вывод типа вывода

Вывод типа вывода выполняется из выражения E с типом T следующим образом:

· Если E является анонимной функцией с полученным типом возвращаемого значения U (§7.4.2.11), а T является типом делегата или типом дерева выражения с типом возвращаемого значения Tb, то выполняется вывод из U для Tb по нижней границе (§7.4.2.9).

· Иначе, если E является группой методов, а T является типом делегата или типом возвращаемого значения дерева выражения Tb с типами параметров T1…Tk и типом возвращаемого значения Tb, и разрешение перегрузки E с типами T1…Tk дает один метод с типом возвращаемого значения U, то выполняется вывод из U для Tb по нижней границе.

· Иначе, если e является выражением с типом U, то выполняется вывод из U для T по нижней границе.

· Иначе вывод не производится.

7.4.2.7 Вывод явных типов параметров

Вывод явных типов параметров выполняется из выражения E с типом T следующим образом.

Если E является анонимной функцией c явным указанием типа с типами параметров U1…Uk и T является типом делегата с типами параметров V1…Vk, то для каждого типа Ui выполняется точный вывод (§7.4.2.8) из Ui для соответствующего Vi.

7.4.2.8 Точный вывод

Точный вывод из типа U для типа V выполняется следующим образом.

· Если V является одним из нефиксированных Xi, то U добавляется к набору границ для Xi.

· Иначе, если U является типом массива Ue[…] и V является типом массива Ve[…] одного ранга, выполняется точный вывод из Ue в Ve.

· Иначе, если V является сформированным типом C<V1…Vk> и U является сформированным типом C<U1…Uk>, то выполняется точный вывод из каждого типа Ui в соответствующий Vi.

· Иначе вывод не производится.

7.4.2.9 Вывод нижних границ

Вывод нижних границ из типа U для типа V выполняется следующим образом.

· Если V является одним из нефиксированных Xi, то U добавляется к набору границ для Xi.

· Иначе, если U является типом массива Ue[…] и V является типом массива Ve[…] такого же ранга, либо если U является типом одномерного массива Ue[]и V является одним из интерфейсов IEnumerable<Ve>, ICollection<Ve> или IList<Ve>, то

o если известно, что Ue является ссылочным типом, то выполняется вывод из Ue в Ve по нижней границе;

o иначе выполняется точный вывод из Ue в Ve.

· Иначе, если V является сформированным типом C<V1…Vk> и имеется уникальный набор типов U1…Uk, такой что существует стандартное неявное преобразование из U в C<U1…Uk>, выполняется точный вывод из каждого Ui для соответствующего Vi.

· Иначе вывод не производится.

7.4.2.10 Фиксирование

Нефиксированная переменная типа Xi с набором границ фиксируется следующим образом.

· Набор типов кандидатов Uj изначально формируется как набор всех типов в наборе границ для Xi.

· Затем по очереди проверяется каждая граница для Xi: для каждой границы U для Xi из набора кандидатов удаляются все типы Uj, в которые не существует стандартного неявного преобразования из U.

· Если среди оставшихся типов-кандидатов Uj имеется уникальный тип V, из которого существует стандартное неявное преобразование во все остальные типы-кандидаты, то Xi фиксируется в V.

· Иначе вывод типа завершается сбоем.

7.4.2.11 Выведенный тип возвращаемого значения

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

· Если телом F является выражение, то выведенный тип возвращаемого значения F совпадет с типом этого выражения.

· Если телом F является блок, а самым подходящим общим типом для набора выражений в операторах return блока является T (§7.4.2.13), то выведенным типом возвращаемого значения F будет T.

· Иначе тип возвращаемого значения для E вывести нельзя.

В качестве примера вывода типа с анонимной функцией рассмотрим метод расширения Select, объявленный в классе System.Linq.Enumerable:

namespace System.Linq
{
public static class Enumerable
{
public static IEnumerable<TResult> Select<TSource,TResult>(
this IEnumerable<TSource> source,
Func<TSource,TResult> selector)
{
foreach (TSource element in source) yield return selector(element);
}
}
}

Предположим, что пространство имен System.Linq было импортировано с помощью предложения using и у класса Customer свойство Name имеет тип string. Тогда для выбора имен списка клиентов можно использовать метод Select:

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

Вызов метода расширения (§7.5.5.2) для Select обрабатывается путем перезаписи вызова статического метода:

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

Поскольку аргументы типа не были указаны явно, для получения аргументов типа используется вывод типа. Сначала аргумент customers связывается с параметром source, и для T выводится тип Customer. Затем с помощью описанного выше процесса вывода типа анонимной функции c присваивается тип Customer, а выражение c.Name связывается с типом возвращаемого значения параметра selector, и для S выводится тип string. Таким образом, этот вызов эквивалентен

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

а результат имеет тип IEnumerable<string>.

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

static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) {
return f2(f1(value));
}

вывод типа при вызове

double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);

будет происходить следующим образом. Сначала аргумент «1:15:30» связывается с параметром value, и для X выводится тип string. Затем параметру первой анонимной функции s присваивается выведенный тип string, а выражение TimeSpan.Parse(s) связывается с типом возвращаемого значения f1, и для Y выводится тип System.TimeSpan. Наконец, параметр второй анонимной функции t получает выведенный тип System.TimeSpan, а выражение t.TotalSeconds связывается с типом возвращаемого значения f2, и для Z выводится тип double. Таким образом, результат вызова имеет тип double.

7.4.2.12 Вывод типа при преобразовании групп методов

Аналогично вызовам универсальных методов вывод типа также должен применяться, когда группа методов M, включающая универсальный метод, преобразуется в данный тип делегата D (§6.6). Если имеется метод

Tr M<X1…Xn>(T1 x1 … Tm xm)

и группа методов M назначается типу делегата D, то задачей вывода типа является поиск аргументов типа S1…Sn, чтобы выражение:

M<S1…Sn>

оказалось совместимым (§15.1) с D.

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

Вместо этого все Xi считаются нефиксированными, и выполняется вывод по нижней границе из каждого типа аргумента Uj для D в соответствующий тип параметра Tj для M. Если для какого-либо Xi границы не были найдены, вывод типа завершается сбоем. Иначе все Xi фиксируются с соответствующими Si, которые являются результатом вывода типа.

7.4.2.13 Поиск наиболее подходящего общего типа для набора выражений

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

При наличии набора выражений E1…Em такой вывод должен быть эквивалентен вызову метода

Tr M<X>(X x1 … X xm)

с Ei в качестве аргументов.

Точнее, вывод начинается с нефиксированной переменной типа X. Вывод типа вывода выполняется из каждого выражения Ei с типом X. Наконец, X фиксируется и получившийся тип S становится общим типом для выражений.

7.4.3 Разрешение перегрузки

Разрешение перегрузки — это механизм времени компиляции для выбора наиболее подходящей функции-члена для вызова по предоставленному списку аргументов и набору кандидатов функций-членов. Разрешение перегрузки позволяет выбрать функцию-член для вызова в следующих отдельных контекстах в C#:

· вызов метода, указанного в выражении_вызова (§7.5.5.1);

· вызов конструктора экземпляра, указанного в выражении_создания_объекта (§7.5.10.1);

· вызов метода доступа к индексатору с помощью доступа_к_элементу (§7.5.6);

· вызов стандартного или пользовательского оператора, на который есть ссылка в выражении (§7.2.3 и §7.2.4).

Для каждого из этих контекстов набор кандидатов функций-членов и список аргументов определяются по-своему, как подробно описывается в разделах выше. Например, набор кандидатов для вызова метода не включает методы, помеченные с помощью ключевого слова override (§7.3), кроме того в список кандидатов не попадают методы базового класса, если применим любой из методов производного класса (§7.5.5.1).

После определения кандидатов функций-членов и списка аргументов выбор наиболее подходящей функции-члена осуществляется одинаковым образом для всех случаев.

· При наличии набора применимых кандидатов функций-членов определяется наиболее подходящая функция-член. Если набор содержит только одну функцию-член, то наиболее подходящей функцией-членом является эта функция. Иначе наиболее подходящей функцией-членом является функция-член, более всего подходящая по списку аргументов в соответствии с правилами из раздела §7.4.3.2. Если не удалось найти одну функцию-член, более подходящую, чем остальные, то вызов функции-члена оказывается неоднозначным и возникает ошибка времени компиляции.

В следующих разделах определяется значение терминов применимая функция-член и более подходящая функция-член.

7.4.3.1 Применимая функция-член

Функция-член называется применимой функцией-членом в соответствии со списком аргументов A, когда выполняются все следующие условия.

· Число аргументов в A равно числу параметров в объявлении функции-члена.

· Для каждого аргумента в A режим передачи параметра для аргумента (то есть по значению, с помощью ключевых слов ref или out) совпадает с режимом передачи соответствующего параметра и

o для параметра значения или массива параметров существует неявное преобразование (§6.1) из аргумента в тип соответствующего параметра или

o для параметров, передаваемых с помощью ref или out, тип аргумента идентичен типу соответствующего параметра. В конечном счете параметры типа ref или out представляют собой псевдонимы для передаваемых аргументов.

Для функции-члена, включающей массив параметров, если функция-член применима в соответствии с указанными правилами, то она называется применимой в нормальной форме. Если функция-член, включающая массив параметров, не применима в нормальной форме, то она может быть применима в расширенной форме.

· Расширенная форма создается путем замены массива параметров в объявлении функции-члена на ноль или другое число параметров с типом элементов массива параметров, так чтобы число аргументов в списке аргументов A совпадало с общим числом параметров. Если в списке A меньше аргументов, чем число фиксированных параметров в объявлении функции-члена, то создать расширенную форму функции-члена нельзя и поэтому она неприменима.

· Иначе расширенная форма применима, если для каждого аргумента в списке A режим передачи параметра для аргумента идентичен режиму передачи соответствующего параметра и

o для фиксированного параметра значения или параметра значения, созданного при расширении, существует неявное преобразование (§6.1) из типа аргумента в тип соответствующего параметра или

o для параметров, передаваемых с помощью ref или out, тип аргумента идентичен типу соответствующего параметра.

7.4.3.2 Более подходящая функция-член

При наличии списка аргументов A с набором выражений аргументов { E1, E2, ..., EN } и двух применимых функций-членов MP и MQ с типами параметров { P1, P2, ..., PN } и { Q1, Q2, ..., QN } MP считается более подходящей функцией-членом, чем MQ, если

· для каждого аргумента неявное преобразование из EX в QX не лучше неявного преобразования из EX в PX и

· по крайней мере для одного аргумента преобразование из EX в PX лучше преобразования из EX в QX.

При проведении этой оценки если MP или MQ применима в расширенной форме, то PX или QX относится к параметру в расширенной форме списка параметров.

Если последовательности типов параметров {P1, P2, …, PN} и {Q1, Q2, …, QN} идентичны, то для определения более подходящей функции-члена применяются следующие правила разрешения.

· Если MP не является универсальным методом, а MQ — универсальный метод, то MP является более подходящей, чем MQ.

· Иначе, если MP применима в нормальной форме, а MQ имеет массив params и применима только в расширенной форме, то MP является более подходящей, чем MQ.

· Иначе, если у MP меньше объявленных параметров, чем у MQ, то MP является более подходящей, чем MQ. Это может просходить, когда у обеих функций-членов есть массивы params, применимые только в расширенных формах.

· Иначе, если у MP более конкретные типы параметров, чем у MQ, то MP является более подходящей, чем MQ. Пусть наборы {R1, R2, …, RN} и {S1, S2, …, SN} представляют неинициализированные и нерасширенные типы параметров функций-членов MP и MQ. Типы параметров MP являются более конкретными, чем у MQ, если для любого параметра RX является не менее конкретным, чем SX, и по крайней мере один параметр RX является более конкретным, чем SX.

o Параметр типа является менее конкретным, чем не параметр типа.

o Один сформированный тип является более конкретным, чем другой (с таким же числом аргументов типа), если у первого типа по крайней мере один аргумент типа является более конкретным по сравнению с соответствующим аргументом типа второго типа и нет аргументов типа менее конкретных по сравнению с соответствующими аргументами типа другого сформированного типа.

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

· Иначе, если один член не является оператором с нулификацией, а другой является, то оператор без нулификации считается более подходящим.

· Иначе определить более подходящую функцию-член нельзя.

7.4.3.3 Лучшее преобразование из выражения

При наличии неявного преобразования C1, которое переводит выражение E в тип T1, и неявного преобразования C2, которое переводит выражение E в тип T2, лучшее преобразование из двух определяется следующим образом.

· Если T1 и T2 — это один тип, ни одно из преобразований не считается лучшим.

· Если E имеет тип S и преобразование из S в T1 лучше преобразования из S в T2, то C1 является лучшим преобразованием.

· Если E имеет тип S и преобразование из S в T2 лучше преобразования из S в T1, то C2 является лучшим преобразованием.

· Если E является анонимной функцией, T1 и T2 являются типами делегатов или типами дерева выражения с идентичными списками параметров и в контексте этого списка параметров (§7.4.2.11) для E существует выведенный тип возвращаемого значения X:

· если T1 имеет тип возвращаемого значения Y1, а T2 имеет тип возвращаемого значения Y2 и преобразование из X в Y1 лучше преобразования из X в Y2, то C1 является лучшим преобразованием;

· если T1 имеет тип возвращаемого значения Y1, а T2 имеет тип возвращаемого значения Y2 и преобразование из X в Y2 лучше преобразования из X в Y1, то C2 является лучшим преобразованием;

· если T1 имеет тип возвращаемого значения Y, а T2 возвращает значение void, то C1 является лучшим преобразованием;

· если T1 возвращает значение void, а T2 имеет тип возвращаемого значения Y, то C2 является лучшим преобразованием.

· Иначе определить лучшее преобразование нельзя.

7.4.3.4 Лучшее преобразование из типа

При наличии преобразования C1, которое переводит тип S в тип T1, и преобразования C2, которое переводит тип S в тип T2, лучшее преобразование из двух определяется следующим образом.

· Если T1 и T2 — это один тип, ни одно из преобразований не считается лучшим.

· Если S имеет тип T1, то лучшим преобразованием является C1.

· Если S имеет тип T2, то лучшим преобразованием является C2.

· Если существует неявное преобразование из T1 в T2 и не существует неявного преобразования из T2 в T1, то лучшим преобразованием является C1.

· Если существует неявное преобразование из T2 в T1 и не существует неявного преобразования из T1 в T2, то лучшим преобразованием является C2.

· Если тип T1 равен sbyte и T2 равен byte, ushort, uint или ulong, то лучшим преобразованием является C1.

· Если тип T2 равен sbyte и T1 равен byte, ushort, uint или ulong, то лучшим преобразованием является C2.

· Если тип T1 равен short и T2 равен ushort, uint или ulong, то лучшим преобразованием является C1.

· Если тип T2 равен short и T1 равен ushort, uint или ulong, то лучшим преобразованием является C2.

· Если тип T1 равен int и T2 равен uint или ulong, то лучшим преобразованием является C1.

· Если тип T2 равен int и T1 равен uint или ulong, то лучшим преобразованием является C2.

· Если тип T1 равен long и T2 равен ulong, то лучшим преобразованием является C1.

· Если тип T2 равен long и T1 равен ulong, то лучшим преобразованием является C2.

· Иначе определить лучшее преобразование нельзя.

· Обратите внимание, что таким образом можно определить лучшее преобразование даже в случаях, когда неявное преобразование не определено. Таким образом, например, преобразование выражения 6 в short будет лучше, чем преобразование 6 в ushort, потому что преобразование любого типа в short лучше преобразования в ushort.

7.4.3.5 Перегрузка в универсальных классах

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

В следующих примерах показываются допустимые и недопустимые в соответствии с этим правилом перегрузки:

interface I1<T> {...}

interface I2<T> {...}

class G1<U>
{
int F1(U u); // При разрешении перегрузки для G<int>.F1
int F1(int i); // будет выбран неуниверсальный метод

void F2(I1<U> a); // допустимая перегрузка
void F2(I2<U> a);
}

class G2<U,V>
{
void F3(U u, V v); // Допустимо, но разрешение перегрузки для
void F3(V v, U u); // G2<int,int>.F3 завершится сбоем

void F4(U u, I1<V> v); // Допустимо, но разрешение перегрузки для
void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 завершится сбоем

void F5(U u1, I1<V> v2); // допустимая перегрузка
void F5(V v1, U u2);

void F6(ref U u); //допустимая перегрузка
void F6(out V v);
}

7.4.4 Вызов функции-члена

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

В целях описания процесса вызова функции-члены разделены на две категории.

· Статические функции-члены. Это конструкторы экземпляров, статические методы, методы доступа к статическим свойствам и пользовательские операторы. Статические функции-члены всегда являются невиртуальными.

· Функции-члены экземпляра. Это методы экземпляра, методы доступа к свойствам и индексаторам. Функции-члены экземпляра могут быть либо виртуальными, либо невиртуальными и всегда вызываются для конкретного экземпляра. Экземпляр вычисляется по выражению экземпляра и оказывается доступен внутри функции-члена с помощью оператора this (§7.5.7).

Обработка вызова функции-члена во время выполнения состоит из следующих этапов, где M —это функция-член, а E — это выражение экземпляра (когда M является членом экземпляра).

· Если M является статической функцией-членом

o Список аргументов вычисляется, как описано в разделе §7.4.1.

o Вызывается M.

· Если M является функцией-членом экземпляра, объявленной в типе_значения

o Вычисляется E. Если при этом вычислении возникает исключение, дальнейшие этапы не выполняются.

o Если E не является переменной, то создается временная локальная переменная типа E и этой переменной присваивается значение E. После этого E становится ссылкой на эту временную локальную переменную. Временная переменная доступна с помощью оператора this в M, но не другим способом. Таким образом, вызывающий может наблюдать изменения, проводимые в M для this, только когда E является настоящей переменной.

o Список аргументов вычисляется, как описано в разделе §7.4.1.

o Вызывается M. Переменная, на которую ссылается E, становится переменной, на которую ссылается this.

· Если M является функцией-членом экземпляра, объявленной в ссылочном_типе

o Вычисляется E. Если при этом вычислении возникает исключение, дальнейшие этапы не выполняются.

o Список аргументов вычисляется, как описано в разделе §7.4.1.

o Если E имеет тип_значения, выполняется преобразование упаковки (§4.3) для преобразования E в тип object. В следующих этапах считается, что E имеет тип object. В данном случае M может быть только членом System.Object.

o Выполняется проверка допустимости значения E. Если E имеет значение null, вызывается исключение System.NullReferenceException и дальнейшие этапы не выполняются.

o Определяется реализация вызываемой функции-члена.

· Если типом E во время компиляции является интерфейс, то вызываемой функцией-членом является реализация M, предоставленная типом времени выполнения экземпляра, на который ссылается E. Эта функция-член определяется применением правил сопоставления интерфейса (§13.4.4), чтобы определить реализацию M, предоставленную типом времени выполнения экземпляра, на который ссылается E.

· Иначе, если M является виртуальной функцией-членом, вызываемая функция-член является реализацией M, предоставленной типом времени выполнения экземпляра, на который ссылается E. Эта функция-член определяется применением правил определения самой производной реализации (§10.6.3) M в соответствии с типом времени выполнения экземпляра, на который ссылается E.

· Иначе M является невиртуальной функцией-членом, и вызываемой функцией-членом является сама M.

o Вызывается реализация функции-члена, определенная на этапе выше. Объект, на который ссылается E, становится объектом, на который ссылается this.

7.4.4.1 Вызов в упакованных экземплярах

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

· Когда функция-член является перегрузкой метода, унаследованного от типа object, и вызывается через выражение экземпляра типа object.

· Когда функция-член является реализацией функции-члена экземпляра и вызывается через выражение экземпляра типа_интерфейса.

· Когда функция-член вызывается через делегат.

В таких ситуациях считается, что упакованный экземпляр содержит переменную с типом_значения и эта переменная становится переменной, на которую ссылается оператор this в вызове функции-члена. В частности, это означает, что при вызове функции-члена в упакованном экземпляре она может изменять значение, хранящееся в упакованном экземпляре.

7.5 Основные выражения

Основные выражения включают самые простые формы выражений.

основное выражение:
основное выражение, отличное от создания массива
выражение создания массива

основное выражение, отличное от создания массива
литерал
простое имя
выражение в скобках
метод доступа к члену
выражение вызова
метод доступа к элементу
метод доступа this
доступ base
выражение инкремента в постпозиции
выражение декремента в постпозиции
выражение создания объекта
выражение создания делегата
выражение создания анонимного объекта
выражение typeof
выражение checked
выражение unchecked
выражение значения по умолчанию
выражение анонимного метода

Основные выражения разделяются на выражения_создания_массива и основные_выражения_отличные_от_выражений_создания_массива Такое выделение выражений создания массива, вместо рассмотрения их вместе с другими простыми видами выражений, позволяет в грамматике запретить потенциально неоднозначный код, ср.:

object o = new int[3][1];

который в противном случае интерпретировался бы как

object o = (new int[3])[1];

7.5.1 Литералы

Основное_выражение, состоящее из литерала (§2.4.4) считается значением.

7.5.2 Простые имена

Просто_имя состоит из идентификатора, за которым может следовать список аргументов типа:

простое имя:
идентификатор список аргументов типанеобяз

Простое_имя имеет вид I или одну из форм I<A1, ..., AK>, где I — это один идентификатор, а <A1, ..., AK> — это необязательный список_аргументов_типа. Когда список_аргументов_типа не указан, считается, что K равно нулю. Простое_имя вычисляется и классифицируется следующим образом.

· Если K равно нулю и простое_имя находится внутри блока и область объявления локальных переменных блока (или включающего блока) (§3.3) содержит локальную переменную, параметр или ограничение с именем I, то простое_имя относится к этой переменной, параметру или константе и считается переменной или значением.

· Если K равно нулю и простое_имя находится внутри тела объявления универсального метода и если это объявление включает параметр типа с именем I, то простое_имя относится к этому параметру типа.

· Иначе для каждого типа экземпляра T (§10.3.1), начиная от типа экземпляра в объявлении непосредственного включающего типа и до типа экземпляра в объявлении каждого включающего класса и структуры (если есть)

o Если K равно нулю и объявление T включает параметр типа с именем I, то простое_имя относится к этому параметру типа.

o Иначе, если при поиске члена I (§7.3) в T с K аргументами типа получены результаты:

· Если T имеет тип экземпляра непосредственного включающего класса или структуры и при поиске находится один или несколько методов, то результатом является группа методов со связанным выражением экземпляра this. Если указан список аргументов типа, он используется при вызове универсального метода (§7.5.5.1).

· Иначе, если T имеет тип экземпляра непосредственного включающего класса или структуры и при поиске находится член экземпляра и если внутри блока конструктора экземпляра, метода экземпляра или метода доступа к экземпляру находится ссылка, то результатом является метод доступа к члену (§7.5.4) в виде this.I. Это происходит, только когда K равно нулю.

· Иначе результатом является метод доступа к члену (§7.5.4) в виде T.I или T.I<A1, ..., AK>. В таком случае если простое_имя будет относиться к члену экземпляра, это будет вызывать ошибку времени компиляции.

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

o Если K равно нулю и I является именем пространства имен в N, то:

· Если местонахождение простого_имени включено в объявление пространства имен для N и объявление пространства имен содержит директиву_псевдонима_extern или директиву_псевдонима_using, которая связывает имя I с пространством имен или типом, тогда простое_имя неоднозначно и возникает ошибка времени компиляции.

· Иначе простое_имя относится к пространству имен I в N.

o Иначе, если N содержит доступный тип с именем I и K параметрами типа, то:

· Если K равно нулю и местонахождение простого_имени включено в объявление пространства имен для N и объявление пространства имен содержит директиву_псевдонима_extern или директиву_псевдонима_using, которая связывает имя I с пространством имен или типом, тогда простое_имя неоднозначно и возникает ошибка времени компиляции.

· Иначе имя_пространства_имен_или_типа относится к типу, сформированному с данными аргументами типа.

o Иначе, если местонахождение простого_имени включено в объявление пространства имен для N, то:

· Если K равно нулю и объявление пространства имен содержит директиву_псевдонима_extern или директиву_псевдонима_using, которая связывает имя I с импортированным пространством имен или типом, тогда простое_имя относится к этому пространству имен или типу.

· Иначе, если пространства имен, импортированные с помощью директив_using_namespace в объявлении пространства имен, содержат ровно один тип с именем I и K параметрами типа, тогда простое_имя относится к этому типу, сформированному с данными аргументами типа.

· Иначе, если пространства имен, импортированные с помощью директив_using_namespace в объявлении пространства имен, содержат более одного типа с именем I и K параметрами типа, тогда простое_имя неоднозначно и возникает ошибка.

Обратите внимание, что этот этап полностью повторяет соответствующий этап в обработке имени_пространства_имен_или_типа (§3.8).

· Иначе простое_имя не определено и возникает ошибка времени компиляции.

7.5.2.1 Инвариантность значения в блоках

Для каждого вхождения идентификатора в качестве простого_имени в выражении или деклараторе любое другое вхождение этого же идентификатора в виде простого_имени в выражении или деклараторе внутри непосредственного включающего блока (§8.2) или блока_switch (§8.7.2) должно указывать на ту же сущность. Это правило позволяет гарантировать, что внутри блока имя всегда имеет одинаковое значение.

В примере

class Test
{
double x;

void F(bool b) {
x = 1.0;
if (b) {
int x;
x = 1;
}
}
}

возникнет ошибка времени компиляции, потому что x относится к другим сущностям во внешнем блоке (часть которого включает вложенный блок в операторе if). Наоборот, в примере

class Test
{
double x;

void F(bool b) {
if (b) {
x = 1.0;
}
else {
int x;
x = 1;
}
}
}

ошибки не возникает, поскольку имя x не используется во внешнем блоке.

Обратите внимание, что правило инвариантности значения применяется только к простым именам. Один идентификатор вполне может иметь одно значение в виде простого имени и другое значение в виде правого операнда в методе доступа к члену (§7.5.4). Например:

struct Point
{
int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

В примере выше демонстрируется общий шаблон использования имен полей в качестве имен параметров в конструкторе экземпляра. В примере простые имена x и y относятся к параметрам, но это не мешает выражениям доступа к членам this.x и this.y иметь доступ к полям.

7.5.3 Выражения со скобками

Выражение_со_скобками состоит из выражения, заключенного в скобки.

выражение со скобками
( выражение )

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

7.5.4 Метод доступа к члену

Метод_доступа_к_члену состоит из основного_выражения, стандартного_типа или уточненного_члена_псевдонима, за которым следует точка «.» и идентификатор. В конце может находиться список_аргументов_типа.

метод доступа к члену
основное выражение . идентификатор список аргументов типанеобяз
стандартный тип . идентификатор список аргументов типанеобяз
уточненный член псевдонима . идентификатор

стандартный тип: один из
bool byte char decimal двойная float int long
object sbyte short строка uint ulong ushort

Создание уточненного_члена_псевдонима определяется в разделе §9.7.

Метод_доступа_к_члену имеет вид E.I или одну из форм E.I<A1, ..., AK>, где E — это основное выражение, I — один идентификатор, а <A1, ..., AK> — это необязательный список_аргументов_типа. Когда список_аргументов_типа не указан, считается, что K равно нулю. Результат метода_доступа_к_члену вычисляется и классифицируется следующим образом.

· Если K равно нулю, E является пространством имен и E содержит вложенное пространство имен I, то результатом является это пространство имен.

· Иначе, если E является пространством имен, E содержит доступный тип с именем I и K параметрами типа, то результатом является этот тип, сформированный с указанными аргументами типа.

· Если E является стандартным_типом или основным_выражением, которое классифицируется как тип, и если E не является параметром типа и если при поиске члена I (§7.3) в E с K параметрами типа было найдено соответствие, то E.I вычисляется и классифицируется следующим образом.

o Если I обозначает тип, то результатом является этот тип, сформированный с указанными аргументами типа.

o Если I обозначает один или несколько методов, то результатом является группа методов без связанного выражения экземпляра. Если указан список аргументов типа, он используется при вызове универсального метода (§7.5.5.1).

o Если I обозначает свойство типа static, то результатом является метод доступа к свойству без связанного выражения экземпляра.

o Если I обозначает поле типа static, то

· Если поле предназначено только для чтения и вне статического конструктора класса или структуры, в которой объявляется поле, имеется ссылка, то результатом является значение, а именно значение статического поля I в E.

· Иначе результатом является переменная, а именно статическое поле I в E.

o Если I обозначает событие типа static, то

· Если внутри класса или структуры, в которой объявляется событие, имеется ссылка и это событие было объявлено без объявлений_метода_доступа_к_событию (§10.8), то E.I обрабатывается точно так же, как если бы I было статическим полем.

· Иначе результатом является метод доступа к событию без связанного выражения экземпляра.

o Если I обозначает константу, то результатом является значение, а именно значение этой константы.

o Если I обозначает член перечисления, то результатом является значение, а именно значение этого члена перечисления.

o Иначе E.I является недопустимой ссылкой на член, и возникает ошибка времени компиляции.

· Если E является методом доступа к свойству или индексатору, переменной или значением с типом T и при поиске члена I (§7.3) в T с K аргументами типа было найдено соответствие, то E.I вычисляется и классифицируется следующим образом.

o Во-первых, если E является свойством или методом доступа к индексатору, то происходит получение значения свойства или метода доступа к индексатору (§7.1.1) и класс E меняется на значение.

o Если I обозначает один или несколько методов, то результатом является группа методов со связанным выражением экземпляра E. Если указан список аргументов типа, то он используется при вызове универсального метода (§7.5.5.1).

o Если I обозначает свойство экземпляра, то результатом является метод доступа к свойству со связанным выражением экземпляра E.

o Если T является типом_класса и I обозначает поле экземпляра этого типа_класса, то

· Если значение E равно null, то вызывается исключение System.NullReferenceException.

· Иначе, если поле предназначено только для чтения и вне статического конструктора класса или структуры, в которой объявляется поле, имеется ссылка, то результатом является значение, а именно значение поля I в объекте, на который ссылается E.

· Иначе результатом является переменная, а именно поле I в объекте, на который ссылается E.

o Если T является типом_структуры и I обозначает поле экземпляра этого типа_структуры, то

· Если E является значением или если поле предназначено только для чтения и вне статического конструктора класса или структуры, в которой объявляется поле, имеется ссылка, то результатом является значение, а именно значение поля I в экземпляре структуры, на который ссылается E.

· Иначе результатом является переменная, а именно поле I в экземпляре структуры, предоставленном E.

o Если I обозначает событие экземпляра, то

· Если внутри класса или структуры, в которой объявляется событие, имеется ссылка и это событие было объявлено без объявлений_метода_доступа_к_событию (§10.8), то E.I обрабатывается точно так же, как если бы I было полем экземпляра.

· Иначе результатом является метод доступа к событию со связанным выражением экземпляра E.

· Иначе выполняется попытка обработать E.I как вызов метода расширения (§7.5.5.2). Если такой вызов завершается сбоем, E.I является недопустимой ссылкой на член и возникает ошибка времени компиляции.

7.5.4.1 Идентичные простые имена и имена типов

В методе доступа к члену в виде E.I, где E является простым идентификатором, а значение E в качестве простого_имени (§7.5.2) является константой, полем, свойством, локальной переменной или параметром того же типа, что и значение E в качестве имени_типа (§3.8), то допустимы оба возможных значения E. Два возможных значения E.I никогда не являются неоднозначными, поскольку I в обоих случаях обязательно должен быть членом типа E. Другими словами, это правило просто разрешает доступ к статическим членам и вложенным типам E, когда в противном случае возникла бы ошибка времени компиляции. Например:

struct Color
{
public static readonly Color White = new Color(...);
public static readonly Color Black = new Color(...);

public Color Complement() {...}
}

class A
{
public Color Color; // Поле Color типа Color

void F() {
Color = Color.Black; // Ссылка на статический член Color.Black
Color = Color.Complement(); // Вызов Complement() для поля Color
}

static void G() {
Color c = Color.White; // Ссылка на статический член Color.White
}
}

Внутри класса A вхождения идентификатора Color, которые ссылаются на тип Color, подчеркиваются, а которые ссылаются на поле Color, не подчеркиваются.

7.5.4.2 Грамматическая неоднозначность

Обработка простого_имени (§7.5.2) и метода_доступа_к_члену (§7.5.4) может привести к появлению неоднозначности в грамматике выражений. Например, оператор

F(G<A,B>(7));

может интерпретироваться как вызов F с двумя аргументами, G < A и B > (7). Иначе он может интерпретироваться как вызов F с одним аргументом, который представляет собой вызов универсального метода G с двумя аргументами типа и одним обычным аргументом.

Если последовательность лексем (в контексте) можно разобрать как простое_имя (§7.5.2), метод_доступа_к_члену (§7.5.4) или метод_доступа_к_указателю_члена (§18.5.2), заканчивающийся списком_аргументов_типа (§4.4.1), то выполняется проверка лексемы, следующей непосредственно за закрывающей лексемой >. Если это одна из лексем

( ) ] } : ; , . ? == !=

то список_аргументов_типа остается в составе простого_имени, метода_доступа_к_члену или метода_доступа_к_указателю_члена, а любой другой возможный разбор последовательности лексем игнорируется. Иначе список_аргументов_типа не считается частью простого_имени, метода_доступа_к_члену или метода_доступа_к_указателю_члена, даже если не существует другого возможного разбора последовательности лексем. Обратите внимание, что эти правила не применяются при разборе списка_аргументов_типа в имени_пространства_имен_или_типа (§3.8). Оператор

F(G<A,B>(7));

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

F(G < A, B > 7);
F(G < A, B >> 7);

будет интерпретирован как вызов F с двумя аргументами. Оператор

x = F < A > +y;

будет интерпретирован как оператор «меньше чем», оператор «больше чем» и унарный оператор «плюс», как если бы выражение было записано в виде x = (F < A) > (+y), а не в виде простого_имени со списком_аргументов_типа, за которым следует бинарный оператор «плюс». В операторе

x = y is C<T> + z;

лексемы C<T> интерпретируются как имя_пространства_имен_или_типа со списком_аргументов_типа.

7.5.5 Выражения вызова

Выражение_вызова используется для вызова метода.

выражение вызова
основное выражение ( список аргументовнеобяз )

Основное_выражение_выражения_вызова должно быть группой методов или значением типа_делегата. Если основное_выражение является группой методов, то выражение_вызова является вызовом метода (§7.5.5.1). Если основное_выражение является значением типа_делегата, то выражение_вызова является вызовом делегата (§7.5.5.3). Если основное_выражение не является ни группой методов, ни значением типа_делегата, то возникает ошибка времени компиляции.

Необязательный список_аргументов (§7.4.1) содержит значения или ссылки на переменные для параметров метода.

Результат вычисления выражения_вызова классифицируется следующим образом.

· Если выражение_вызова вызывает метод или делегат, который возвращает значение типа void, то результат отсутствует. Выражение, не имеющее класса, не может быть операндом какого бы то ни было оператора и может использоваться только в контексте выражения_оператора (§8.6).

· Иначе результатом является значение типа, возвращенного методом или делегатом.

7.5.5.1 Вызовы методов

Для вызова метода основное_выражение_выражения_вызова должно быть группой методов. Группа методов определяет один вызываемый метод или набор перегруженных методов, из которых будет выбран конкретный вызываемый метод. В последнем случае решение о конкретном вызываемом методе основывается на контексте, предоставляемом типами аргументов в списке_аргументов.

Во время компиляции обработка вызова метода в виде M(A), где M является группой методов (возможно, включающей список_аргументов_типов), а A является необязательным списком_аргументов, включает следующие этапы.

· Формируется набор методов-кандидатов для вызова метода. Для каждого метода F, связанного с группой методов M

o Если F является неуниверсальным методом, то F является кандидатом, когда:

· у M нет списка аргументов типа и

· F является применимым в соответствии со списком A (§7.4.3.1).

o Если F является универсальным методом, а у M нет списка аргументов типа, то F является кандидатом, когда:

· успешно завершается вывод типа (§7.4.2), предоставляющий список аргументов типа для вызова, и

· после замены выведенных аргументов типа на соответствующие параметры типа для метода все сформированные типы в списке параметров F соответствуют своим ограничениям (§4.4.4), а список параметров F применим в соответствии со списком A (§7.4.3.1).

o Если F является универсальным методом, а M включает список аргументов типа, то F является кандидатом, когда:

· число параметров типа для метода F равно числу аргументов, предоставленному в списке аргументов типа, и

· после замены аргументов типа на соответствующие параметры типа для метода все сформированные типы в списке параметров F соответствуют своим ограничениям (§4.4.4), а список параметров F применим в соответствии со списком A (§7.4.3.1).

· Набор методов-кандидатов сокращается только до методов из наиболее производных типов. Для каждого метода C.F в наборе, где C является типом, в котором определен метод F, все методы, объявленные в базовом типе C из набора удаляются. Более того, если C является типом класса, отличным от object, то все методы, объявленные в типе интерфейса из набора удаляются. (Последнее правило применяется только, когда группа методов является результатом поиска члена по параметру типа при наличии действительного базового класса, отличного от object, и непустого действительного набора интерфейсов.)

· Если результирующий набор методов-кандидатов пуст, то дальнейшая обработка вместе со следующими этапами не выполняется, а вместо этого предпринимается попытка обработать вызов в виде вызова метода расширения (§7.5.5.2). Если такая обработка не удается, то применимых методов не существует и возникает ошибка времени компиляции.

· Если методы-кандидаты не все объявлены в одном типе, то вызов метода является неоднозначным и возникает ошибка времени компиляции. (Данная ситуация может происходить только при вызове метода в интерфейсе, у которого есть несколько непосредственных базовых интерфейсов, как описано в разделе §13.2.5, или при вызове метода с параметром типа.)

· Лучший метод из набора методов-кандидатов определяется с помощью правил разрешения перегрузки из раздела §7.4.3. Если определить один лучший метод нельзя, то вызов метода является неоднозначным и возникает ошибка времени компиляции. При проведении разрешения перегрузки параметры универсального метода учитываются после замены аргументов типа (предоставленных или выведенных) на соответствующие параметры типа для метода.

· Выполняется последняя проверка выбранного лучшего метода.

o Метод проверяется в контексте группы методов. Если базовый метод является статическим методом, то группа методов должна формироваться на основании простого_имени или метода_доступа_к_члену по типу. Если лучший метод является методом экземпляра, то группа методов должна формироваться на основании простого_имени, метода_доступа_к_члену по переменной или значению или на основании метода_базового_доступа. Если не выполняется ни одно из этих требований, то возникает ошибка времени компиляции.

o Если лучший метод является универсальным методом, то выполняется проверка аргументов типа (предоставленных или выведенных) в соответствии с ограничениями (§4.4.4), объявленными в универсальном методе. Если какой-либо аргумент типа не удовлетворяет соответствующим ограничениям для типа параметра, то возникает ошибка времени компиляции.

После выбора и проверки метода в соответствии с указанными этапами на этапе компиляции обрабатывается фактический вызов времени выполнения в соответствии с правилами вызова функций-членов из раздела §7.4.4.

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

7.5.5.2 Вызовы методов расширения

Для вызова метода (§7.5.5.1) одного из видов

выр. идентификатор ( )

выр. идентификатор ( аргументы )

выр. идентификатор < аргументы типа > ( )

выр. идентификатор < аргументы типа > ( аргументы )

если при обычной обработке вызова применимые методы не найдены, то предпринимается попытка обработать конструкцию в виде вызова метода расширения. Задачей является найти лучшее имя_типа C, чтобы можно было вызвать соответствующий статический метод:

C. идентификатор ( выражение )

C. идентификатор ( выражение, аргументы )

C. идентификатор < аргументы типа > ( выражение )

C. идентификатор < аргументы типа > ( выражение, аргументы )

Поиск C выполняется следующим образом.

· Предпринимаются попытки найти набор кандидатов методов расширения, начиная с ближайшего объявления включающего пространства имен, с каждого включающего объявления пространства имен, и заканчивая включающим скомпилированным модулем.

o Если данное пространство имен или скомпилированный модуль непосредственно содержат объявления неуниверсального типа Ci с методами расширения Mj с именем идентификатор, которые являются доступными и применимыми в соответствии с нужным вызовом статического метода (см. выше), то набором кандидатов является набор таких методов расширения.

o Если пространства имен, импортированные с помощью соответствующих директив в указанном пространстве имен или скомпилированном модуле, непосредственно содержат объявления неуниверсального типа Ci с методами расширения Mj с именем идентификатор, которые являются доступными и применимыми в соответствии с нужным вызовом статического метода (см. выше), то набором кандидатов является набор таких методов расширения.

· Если ни в одном включающем объявлении пространства имен или скомпилированном модуле набор кандидатов не найден, то возникает ошибка времени компиляции.

· Иначе к набору кандидатов применяется разрешение перегрузки, как описано в разделе (§7.4.3). Если один лучший метод не найден, то возникает ошибка времени компиляции.

· C — это тип, в котором лучший метод объявляется в качестве метода расширения.

· После этого с помощью C выполняется вызов метода в виде вызова статического метода (§7.4.4).

Указанные выше правила означают, что методы экземпляра имеют приоритет по сравнению с методами расширения, что методы расширения, доступные во внутренних объявлениях пространств имен, имеют приоритет по сравнению с методами расширения, доступными во внешних объявлениях пространств имен, и что методы расширения, объявленные непосредственно в пространстве имен, имеют приоритет над методами расширения, импортированными в это же пространство имен с помощью директивы пространства имен. Например:

public static class E
{
public static void F(this object obj, int i) { }

public static void F(this object obj, string s) { }
}

class A { }

class B
{
public void F(int i) { }
}

class C
{
public void F(object obj) { }
}

class X
{
static void Test(A a, B b, C c) {
a.F(1); // E.F(object, int)
a.F("hello"); // E.F(object, string)

b.F(1); // B.F(int)
b.F("hello"); // E.F(object, string)

c.F(1); // C.F(object)
c.F("hello"); // C.F(object)
}
}

В этом примере метод B имеет приоритет по сравнению с первым методом расширения, а метод C имеет приоритет над обоими методами расширения.

public static class C
{
public static void F(this int i) { Console.WriteLine("C.F({0})", i); }
public static void G(this int i) { Console.WriteLine("C.G({0})", i); }
public static void H(this int i) { Console.WriteLine("C.H({0})", i); }
}

namespace N1
{
public static class D
{
public static void F(this int i) { Console.WriteLine("D.F({0})", i); }
public static void G(this int i) { Console.WriteLine("D.G({0})", i); }
}
}

namespace N2
{
using N1;

public static class E
{
public static void F(this int i) { Console.WriteLine("E.F({0})", i); }
}

class Test
{
static void Main(string[] args)
{
1.F();
2.G();
3.H();
}
}
}

Вывод в данном примере имеет вид:

E.F(1)
D.G(2)
C.H(3)

D.G имеет приоритет над C.G, а E.F имеет приоритет над D.F и C.F.

7.5.5.3 Вызовы делегатов

Для вызова метода основное_выражение_выражения_вызова должно быть значением типа_делегата. Кроме того, учитывая, что тип_делегата должен быть функцией-членом с таким же списком параметров, что и тип_делегата, он должен быть применим (§7.4.3.1) в соответствии со списком_аргументов_выражения_вызова.

Во время выполнения обработка вызова делегата в виде D(A), где D является основным_выражением_типа_делегата, а A является необязательным списком_аргументов, включает следующие этапы.

· Вычисляется D. Если при этом вычислении возникает исключение, дальнейшие этапы не выполняются.

· Выполняется проверка допустимости значения D. Если D имеет значение null, вызывается исключение System.NullReferenceException и дальнейшие этапы не выполняются.

· Иначе D является ссылкой на экземпляр делегата. Вызовы функций-членов (§7.4.4) выполняются для каждой вызываемой сущности в списке вызова делегата. Для вызываемых сущностей, состоящих из экземпляра и метода экземпляра, вызываемый экземпляр является экземпляром, содержащимся в вызываемой сущности.

7.5.6 Метод доступа к элементу

Метод_доступа_к_элементу состоит из основного_выражения_отличного_от_создания_массива, за которым следует лексема «[» и список_выражений, за которым следует лексема «]». Список_выражений содержит одно или несколько выражений, разделенных запятыми.

метод доступа к элементу:
основное выражение, отличное от создания массива [ список выражений ]

список выражений:
выражение
список выражений , выражение

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

7.5.6.1 Доступ к массиву

При доступе к массиву основное_выражение_отличное_от_создания_массива, для метода_доступа_к_элементу должно быть значением типа_массива. Число выражений в списке_выражений должно соответствовать размерности типа_массива, и каждое выражение должно иметь тип int, uint, long, ulong или тип, неявно преобразуемый в один или несколько этих типов.

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

Во время выполнения обработка доступа к массиву в виде P[A], где P является основным_выражением_отличным_от_создания_массива, с типом_массива, а A является списком_выражений, включает следующие этапы.

· Вычисляется P. Если при этом вычислении возникает исключение, дальнейшие этапы не выполняются.

· Вычисляются выражения индекса в списке_выражений по порядку слева направо. После вычисления всех выражений индекса выполняется неявное преобразование (§6.1) в один из следующих типов: int, uint, long, ulong. Выбирается первый тип из этого списка, для которого существует неявное преобразование. Например, если выражение индекса имеет тип short, то выполняет неявное преобразование в тип int, поскольку возможны неявные преобразования из short в int и из short в long. Если при вычислении выражения индекса или последующем неявном преобразовании возникает исключение, то следующие выражения индекса не вычисляются и дальнейшие этапы не выполняются.

· Выполняется проверка допустимости значения P. Если P имеет значение null, вызывается исключение System.NullReferenceException и дальнейшие этапы не выполняются.

· Значение каждого выражения в списке_выражений проверяется на соответствие фактическим границам каждого измерения экземпляра массива, на который ссылается P. Если одно или несколько значений выходят за пределы допустимого диапазона, вызывается исключение System.IndexOutOfRangeException и дальнейшие этапы не выполняются.

· Вычисляется расположение элемента массива, заданного выражениями индекса, и оно становится результатом метода доступа к массиву.

7.5.6.2 Доступ к индексатору

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

Во время компиляции обработка доступа к индексатору в виде P[A], где P является основным_выражением_отличным_от_создания_массива, с типом класса, структуры или интерфейса T, а A является списком_выражений, включает следующие этапы.

· Создается набор индексаторов, предоставляемых T. Этот набор состоит из всех индексаторов, объявленных в T или в базовом для T типе, которые не являются перегруженными объявлениями и доступны в текущем контексте (§3.4.7).

· Этот набор сокращается до индексаторов, которые применимы и не скрыты другими индексаторами. К каждому индексатору S.I в наборе, где S является типом, в котором определен I, применяются следующие правила.

o Если I не применим в соответствии со списком A (§7.4.3.1), то I удаляется из набора.

o Если I применим в соответствии со списком A (§7.4.3.1), то все индексаторы, объявленные в базовом типе для S удаляются из набора.

o Если I применим в соответствии со списком A (§7.4.3.1) и S имеет тип класса, отличный от object, то из набора удаляются все индексаторы, объявленные в интерфейсе.

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

· Лучший индексатор из набора индексаторов-кандидатов определяется с помощью правил разрешения перегрузки из раздела §7.4.3. Если определить один лучший индексатор нельзя, то метод доступа к индексатору является неоднозначным и возникает ошибка времени компиляции.

· Вычисляются выражения индекса в списке_выражений по порядку слева направо. Результатом обработки доступа к индексатору является выражение, классифицированное как доступ к индексатору. Выражение доступа к индексатору ссылается на индексатор, определенный на предыдущем этапе, и имеет связанное выражение экземпляра P и связанный список аргументов A.

В зависимости от контекста использования доступ к индексатору задает вызов метода_доступа_get или метода_доступа_set индексатора. Если доступ к индексатору является назначением присваивания, то для назначения нового значения вызывается метод_доступа_set (§7.16.1). Во всех остальных случаях для получения текущего значения вызывается метод_доступа_get (§7.1.1).

This

Доступ_this представляет собой зарезервированное слово this.

доступ this:
this

Доступ_this допустим только в блоке конструктора экземпляра, метода экземпляра или метода доступа к экземпляру. Он имеет одно из следующих значений.

· Когда доступ this используется в основном_выражении внутри конструктора экземпляра класса, он классифицируется как значение. Типом значения является тип экземпляра (§10.3.1) класса, внутри которого происходит это использование, а значением является ссылка на создаваемый объект.

· Когда доступ this используется в основном_выражении внутри метода экземпляра или метода доступа к экземпляру, он классифицируется как значение. Типом значения является тип экземпляра (§10.3.1) класса, внутри которого возникает это использование, а значением является ссылка на объект, для которого вызывается метод или метод доступа.

· Когда доступ this используется в основном_выражении внутри конструктора экземпляра структуры, он классифицируется как значение. Типом значения является тип экземпляра (§10.3.1) структуры, внутри которой происходит это использование, а значением является создаваемая структура. Переменная this конструктора экземпляра структуры действует точно так же, как параметр out типа структуры, в частности, это означает, что переменная должна явно назначаться в каждом пути выполнения конструктора экземпляра.

· Когда доступ this используется в основном_выражении внутри метода экземпляра или метода доступа к экземпляру структуры, он классифицируется как значение. Типом переменной является тип экземпляра структуры (§10.3.1), внутри которой происходит это использование.

o Если метод или метод доступа не является итератором (§10.14), то переменная this представляет структуру, для которой был вызван метод или метод доступа, и действует точно так же, как параметр ref типа структуры.

o Если метод или метод доступа является итератором, то переменная this представляет копию структуры, для которой был вызван метод или метод доступа, и действует точно так же, как параметр значения типа структуры.

Использование слова this в основном_выражении в контексте, отличном от указанных выше, приводит к возникновению ошибки времени компиляции. В частности, нельзя ссылаться на this в статическом методе, методе доступа к статическому свойству или в инициализаторе_переменной объявления поля.

Base

Доступ_base состоит из зарезервированного слова base, за которым следует либо лексема «.» с идентификатором, либо список_выражений, заключенный в квадратные скобки:

доступ base
base . идентификатор
base [ список выражений ]

Доступ_base используется для доступа к членам базового класса, скрытым членами с такими же именами в текущем классе или структуре. Доступ_base допустим только в блоке конструктора экземпляра, метода экземпляра или метода доступа к экземпляру. Когда вызов base.I происходит в классе или структуре, I должно обозначать член базового класса для данного класса или структуры. Так же, когда вызов base[E] оказывается в классе, в базовом классе должен существовать применимый индексатор.

Во время компиляции выражения доступа_base в виде base.I и base[E] вычисляются точно так же, как если бы они записывались в виде ((B)this).I и ((B)this)[E], где B является базовым классом для класса или структуры, в которой находится эта конструкция. Таким образом, base.I и base[E] соответствуют выражениям this.I и this[E] за исключением того, что this рассматривается как экземпляр базового класса.

Когда в доступе_base оказывается ссылка на виртуальную функцию-член (метод, свойство или индексатор), способ определения вызываемой во время выполнения функции-члена (§7.4.4) меняется. Вызываемая функция-член определяется путем поиска наиболее производной реализации (§10.6.3) функции-члена в соответствии с B (вместо сравнения с типом this во время выполнения, что является обычной процедурой при доступе, отличном от base). Таким образом, внутри виртуальной или перегруженной функции-члена доступ_base можно использовать для вызова наследованной реализации функции-члена. Если функция-член, на которую ссылается доступ_base, является абстрактной, то возникает ошибка времени компиляции.

7.5.9 Постфиксные операторы инкремента и декремента

постфиксное выражение инкремента:
основное выражение ++

постфиксное выражение декремента:
основное выражение --

Операнд постфиксного оператора инкремента или декремента должен быть выражением, которое классифицируется как переменная, доступ к свойству или доступ к индексатору. Результатом операции является значение того же типа, что и операнд.

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

Для выбора конкретной реализации оператора используется разрешение перегрузки унарных операторов (§7.2.3). Стандартные операторы ++ и -- применимы к следующим типам: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal и любым типам перечисления. Стандартный оператор ++ возвращает значение, полученное добавлением 1 к операнду, а стандартный оператор -- возвращает значение, полученное вычитанием 1 из операнда. В контексте checked если результат такого сложения или вычитания выходит за пределы допустимого диапазона для типа результата и результат имеет целый тип или тип перечисления, возникает исключение System.OverflowException.

Во время выполнения обработка постфиксных операций инкремента или декремента в виде x++ или x-- включает следующие этапы.

· Если x классифицируется как переменная, то:

o x вычисляется для создания переменной.

o Значение x сохраняется.

o Вызывается выбранный оператор с сохраненным значением x в качестве аргумента.

o Значение, возвращенное оператором, сохраняется в расположении, предоставленном при вычислении x.

o Сохраненное значение x становится результатом операции.

· Если x классифицируется как свойство или доступ к индексатору, то:

o Вычисляются выражение экземпляра (если x не имеет тип static) и список аргументов (если x является доступом к экземпляру), связанные с x, и полученные результаты используются при последующих вызовах методов доступа get и set.

o Вызывается метод доступа get для x, а возвращенное значение сохраняется.

o Вызывается выбранный оператор с сохраненным значением x в качестве аргумента.

o Вызывается метод доступа set для x со значением, возвращенным оператором в качестве своего аргумента value.

o Сохраненное значение x становится результатом операции.

Операторы ++ и -- также могут использоваться в препозиции (§7.6.5). Результатом операторов x++ и x-- является значение x до операции, тогда как результатом ++x и --x является значение x после операции. В обоих случаях сама переменная x имеет одинаковое значение после операции.

Реализацию оператора ++ или -- можно вызывать в префиксной и постфиксной форме. Для двух этих форм нельзя создать разные реализации операторов.

New

Оператор new используется для создания новых экземпляров типов.

Существует три формы выражений new:

· Выражения создания объектов используются для создания новых экземпляров типов класса и типов значения.

· Выражения создания массивов используются для создания новых экземпляров типов массива.

· Выражения создания делегатов используются для создания новых экземпляров типа делегата.

Оператор new подразумевает создание экземпляра типа, но необязательно подразумевает динамическое выделение памяти. В частности, для экземпляров с типом значения не требуется дополнительная память помимо переменных, в которых они находятся, и при использовании new для создания экземпляров с типом значения динамическое выделение памяти не происходит.

7.5.10.1 Выражения создания объектов

Выражение_создания_объекта используется для создания нового экземпляра типа_класса или типа_значения.

выражение создания объекта:
new тип ( список аргументовнеобяз ) инициализатор объекта или коллекциинеобяз
new тип инициализатор объекта или коллекции

инициализатор объекта или коллекции:
инициализатор объекта
инициализатор коллекции

Тип выражения_создания_объекта должен быть равен типу_класса, типу_значения или параметру_типа. Тип не может быть типом_класса abstract.

Необязательный список_аргументов (§7.4.1) допускается, только если тип равен типу_класса или типу_структуры.

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

При обработке выражения создания объекта, которое включает инициализатор объекта или коллекции, сначала выполняется конструктор экземпляра, а затем выполняется инициализация члена или элемента, указанного в инициализаторе объекта (§7.5.10.2) или коллекции (§7.5.10.3).

Во время компиляции обработка выражения_создания_объекта в виде new T(A), где T является типом_класса или типом_значения, а A является необязательным списком_аргументов, включает следующие этапы.

· Если T имеет тип_значения и A не указан.

o Выражение_создания_объекта является вызовом конструктора по умолчанию. Результатом выражения_создания_объекта является значение типа T, а именно значение T по умолчанию, определенное в §4.1.1.

· Иначе, если T является параметром_типа и A не указан.

o Если для T не были указаны ограничения типа значения или конструктора (§10.1.5), то возникает ошибка времени компиляции.

o Результатом выражения_создания_объекта является значение типа времени выполнения, к которому привязан параметр типа, а именно результат вызов конструктора по умолчанию для этого типа. Тип времени выполнения может быть типом значения или ссылочным типом.

· Иначе, если T является типом_класса или типом_структуры:

o Если T имеет тип_класса abstract, то возникает ошибка времени компиляции.

o Вызываемый конструктор экземпляра определяется с помощью правил разрешения перегрузки из раздела §7.4.3. Набор кандидатов конструкторов экземпляров включает все доступные конструкторы экземпляров, объявленные в T, применимые в соответствии со списком A (§7.4.3.1). Если набор кандидатов конструкторов экземпляров пуст или невозможно определить один лучший конструктор экземпляра, то возникает ошибка времени компиляции.

o Результатом выражения_создания_объекта является значение типа T, а именно значение, получаемое при вызове конструктора экземпляра, определенного на предыдущем этапе.

· Иначе выражение_создания_объекта является недопустимым и возникает ошибка времени компиляции.

Во время выполнения обработка выражения_создания_объекта в виде new T(A), где T является типом_класса или типом_структуры, а A является необязательным списком_аргументов, включает следующие этапы.

· Если T является типом_класса:

o Создается новый экземпляр класса T. Если для создания нового экземпляра недостаточно памяти, то возникает исключение System.OutOfMemoryException и дальнейшие этапы не выполняются

o Все поля нового экземпляра инициализируются с помощью значений по умолчанию (§5.2).

o В соответствии с правилами вызова функции-члена (§7.4.4) вызывается конструктор экземпляра. Ссылка на созданный экземпляр автоматически передается конструктору экземпляра, и к этому экземпляру можно обращаться из этого конструктора с помощью this.

· Если T является типом_структуры:

o С помощью выделения временной локальной переменной создается экземпляр типа T. Поскольку для явного присвоения значений каждому полю создаваемого экземпляра требуется конструктор экземпляра типа_структуры, инициализация временной переменной не требуется.

o В соответствии с правилами вызова функции-члена (§7.4.4) вызывается конструктор экземпляра. Ссылка на созданный экземпляр автоматически передается конструктору экземпляра, и к этому экземпляру можно обращаться из этого конструктора с помощью this.

7.5.10.2 Инициализаторы объектов

Инициализатор объекта задает значения для нуля или нескольких полей или свойств объекта.

инициализатор объекта:
{ список инициализаторов членовнеобяз }
{ список инициализаторов членов , }

список инициализаторов членов
инициализатор члена
список инициализаторов членов , инициализаторов члена

инициализатор члена:
идентификатор = значение идентификатора

значение идентификатора:
выражение
инициализатор объекта или коллекции

Инициализатор объекта состоит из последовательности инициализаторов членов, заключенных между лексемами { и } и разделенных запятыми. В каждом инициализаторе члена должно указываться имя доступного поля или свойства инициализируемого объекта, за которым следует знак равенства и выражение или инициализатор объекта или коллекции. Инициализатор объекта не может содержать более одного инициализатора члена для одного поля или свойства. Инициализатор объекта не может ссылаться на инициализируемый им объект.

Инициализатор члена, в котором после знака равенства указывается выражение, обрабатывается так же, как присваиванием (§7.16.1) полю или свойству.

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

Инициализатор члена, в котором после знака равенства указывается инициализатор коллекции, выполняет инициализацию внедренной коллекции. Вместо назначения полю или свойству новой коллекции элементы, указанные в инициализаторе, добавляются в коллекцию, на которую ссылается поле или свойство. Поле или свойство должно иметь тип коллекции, который удовлетворяет требованиям раздела §7.5.10.3.

Следующий класс представляет собой точку с двумя координатами.

public class Point
{
int x, y;

public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}

Экземпляр Point можно создать и инициализировать следующим образом:

Point a = new Point { X = 0, Y = 1 };

что равносильно

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

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

public class Rectangle
{
Point p1, p2;

public Point P1 { get { return p1; } set { p1 = value; } }
public Point P2 { get { return p2; } set { p2 = value; } }
}

Экземпляр Rectangle можно создать и инициализировать следующим образом:

Rectangle r = new Rectangle {
P1 = new Point { X = 0, Y = 1 },
P2 = new Point { X = 2, Y = 3 }
};

что равносильно

Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;

 

где __r, __p1 и __p2 являются временными переменными, не видимыми и недоступными другим образом.

Если конструктор Rectangle выделяет два внедренных экземпляра Point

public class Rectangle
{
Point p1 = new Point();
Point p2 = new Point();

public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
}

то вместо назначения новых экземпляров для инициализации внедренных экземпляров Point можно использовать следующую конструкцию:

Rectangle r = new Rectangle {
P1 = { X = 0, Y = 1 },
P2 = { X = 2, Y = 3 }
};

что равносильно

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;

7.5.10.3 Инициализаторы коллекции

Инициализатор коллекции задает элементы коллекции.

инициализатор коллекции:
{ список инициализаторов элементов }
{ список инициализаторов элементов , }

список инициализаторов элементов
инициализатор элемента
список инициализаторов элементов, инициализатор элемента

инициализатор элемента:
выражение не присваивания
{ список выражений }

Инициализатор коллекции состоит из последовательности инициализаторов элементов, заключенных между лексемами { и } и разделенных запятыми. Каждый инициализатор элемента задает элемент, добавляемый в инициализируемый объект коллекции, и состоит из списка выражений, заключенных между лексемами { и } и разделенных запятыми. Инициализатор элемента с одним выражением можно записывать без скобок, но в таком случае оно не может быть выражением присваивания, чтобы избежать неоднозначности с инициализаторами членов. Создание выражения_не_присваивания определяется в разделе §7.17.

Ниже приводится пример выражения создания объекта, в которое входит инициализатор коллекции:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Объект коллекции, к которому применяется инициализатор коллекции, должен иметь тип, в котором реализуется интерфейс System.Collections.Ienumerable, иначе будет возникать ошибка времени компиляции. Для каждого указанного элемента по порядку инициализатор вызывает метод Add целевого объекта со списком выражений инициализатора элемента в качестве списка аргументов, применяя обычное разрешение перегрузки для каждого вызова. Таким образом, объект коллекции должен содержать применимый метод Add для каждого инициализатора элемента.

Следующий класс представляет собой контакт с именем и списком телефонных номеров:

public class Contact
{
string name;
List<string> phoneNumbers = new List<string>();

public string Name { get { return name; } set { name = value; } }

public List<string> PhoneNumbers { get { return phoneNumbers; } }
}

Экземпляр List<Contact> можно создать и инициализировать следующим образом:

var contacts = new List<Contact> {
new Contact {
Name = "Chris Smith",
PhoneNumbers = { "206-555-0101", "425-882-8080" }
},
new Contact {
Name = "Bob Harris",
PhoneNumbers = { "650-555-0199" }
}
};

что равносильно

var contacts = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
contacts.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
contacts.Add(__c2);

где __c1 и __c2 являются временными переменными, не видимыми и недоступными другим образом.

7.5.10.4 Выражения создания массива

Выражение_создания_массива используется для создания нового экземпляра типа_массива.

выражение создания массива:
new тип не массива [ список выражений ] спецификация ранганеобяз инициализатор массиванеобяз
new тип массива инициализатор массива
new спецификация ранга инициализатор массива

Выражение создания массива первого типа создает экземпляр массива с типом, который получается после удаления всех отдельных выражений из списка выражений. Например, выражение создания массива new int[10, 20] создает экземпляр массива с типом int[,], а выражение new int[10][,] — экземпляр массива с типом int[][,]. Каждое выражение в списке выражений должно иметь тип int, uint, long или ulong или тип, который может быть явно преобразован в один или несколько этих типов. Значение каждого выражения определяет размер соответствующего измерения в новом созданном экземпляре массива. Поскольку размер измерения массива должен быть неотрицательным, при наличии константного_выражения с отрицательным значением в списке выражений будет возникать ошибка времени компиляции.

За исключением небезопасных контекстов (§18.1) формат массива не указывается.

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

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

new int[,] {{0, 1}, {2, 3}, {4, 5}}

в точности соответствует

new int[3, 2] {{0, 1}, {2, 3}, {4, 5}}

Выражение создания массива третьего типа называется выражением создания массива с неявным указанием типа. Оно похоже на второй тип за исключением того, что тип элемента массива не задается явно, но определяется как наиболее общий тип (§7.4.2.13) в наборе выражений в инициализаторе массива. Для многомерного массива (в спецификации_ранга, у которого есть по крайней мере одна запятая) этот набор включает все выражения, находящиеся во вложенных инициализаторах_массива.

Инициализаторы массива подробнее описываются далее в разделе §12.6.

Результат вычисления выражения создания массива классифицируется как значение, а именно как ссылка на новый созданный экземпляр массива. Во время выполнения обработка выражения создания массива включает следующие этапы.

· Вычисляются выражения длины измерений в списке_выражений по порядку слева направо. После вычисления всех выражений выполняется неявное преобразование (§6.1) в один из следующих типов: int, uint, long, ulong. Выбирается первый тип из этого списка, для которого существует неявное преобразование. Если при вычислении выражения или последующем неявном преобразовании возникает исключение, то следующие выражения не вычисляются и дальнейшие этапы не выполняются.

· Вычисленные значения для длин измерений проверяются следующим образом. Если одно или несколько значений оказываются меньше нуля, то вызывается исключение System.OverflowException и дальнейшие этапы не выполняются.

· Создается экземпляр массива с полученными длинами измерений. Если для создания нового экземпляра недостаточно памяти, то возникает исключение System.OutOfMemoryException и дальнейшие этапы не выполняются

· Все элементы нового экземпляра массива инициализируются с помощью значений по умолчанию (§5.2).

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

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

int[][] a = new int[100][];

создается одномерный массив со 100 элементами типа int[]. Исходным значением каждого элемента является null. В этом же выражении создания массива нельзя инициализировать подмассивы, и выражение

int[][] a = new int[100][5]; //Ошибка

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

int[][] a = new int[100][];
for (int i = 0; i < 100; i++) a[i] = new int[5];

Когда массив массивов имеет «прямоугольную» форму, то есть когда все подмассивы имеют одинаковую длину, более эффективно использовать многомерный массив. В примере выше при инициализации массива массивов создается 101 объект — один внешний массив и 100 вложенных массивов. Напротив, в выражении

int[,] = new int[100, 5];

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

Ниже приведены примеры выражений создания массивов с неявным заданием типа.

var a = new[] { 1, 10, 100, 1000 }; // int[]

var b = new[] { 1, 1.5, 2, 2.5 }; // double[]

var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]

var d = new[] { 1, "one", 2, "two" }; //Ошибка

Последнее выражение вызывает ошибку времени выполнения, поскольку типы int и string не могут быть неявно преобразованы один в другой, поэтому в данном случае наиболее общий тип отсутствует. В этом случае необходимо использовать выражение создания массива с явным заданием типа, например, указав тип object[]. Иначе один из элементов можно привести к общему базовому типу, который затем станет выведенным типом элемента.

Выражения создания массива с неявным заданием типа можно комбинировать с инициализаторами анонимных объектов (§7.5.10.6) для создания структуры данных с анонимным типом Например:

var contacts = new[] {
new {
Name = "Chris Smith",
PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
Name = "Bob Harris",
PhoneNumbers = new[] { "650-555-0199" }
}
};

7.5.10.5 Выражения создания делегата

Выражение_создания_делегата используется для создания нового экземпляра типа_делегата.

выражение создания делегата:
new тип делегата ( выражение )

Аргументом выражения создания делегата должна быть группа методов, анонимная функция или значение типа_делегата. Если аргумент является группой методов, он определяет метод, а для метода экземпляра — объект, для которого создается делегат. Если аргументом является анонимная функция, он напрямую определяет параметры и тело метода целевого делегата. Если аргумент является значением типа_делегата, он определяет экземпляр делегата, для которого создается копия.

Во время компиляции обработка выражения_создания_делегата в виде new D(E), где D имеет тип_делегата, а E является выражением, включает следующие этапы.

· Если E является группой методов, выражение создания делегата обрабатывается так же, как и преобразование группы методов (§6.6) из E в D.

· Если E является анонимной функцией, выражение создания делегата обрабатывается так же, как и преобразование анонимной функции (§6.5) из E в D.

· Если E является значением типа делегата, то E должно быть совместимо (§15.1) с D, а результатом является новый созданный делегат типа D, который ссылается на тот же список вызова, что и E. Если E не совместимо с D, возникает ошибка времени компиляции.

Во время выполнения обработка выражения_создания_делегата в виде new D(E), где D имеет тип_делегата, а E является выражением, включает следующие этапы.

· Если E является группой методов, выражение создания делегата обрабатывается как преобразование группы методов (§6.6) из E в D.

· Если E является анонимной функцией, выражение создания делегата обрабатывается как преобразование анонимной функции из E в D (§6.5).

· Если E является значением типа_делегата:

o Вычисляется E. Если при этом вычислении возникает исключение, дальнейшие этапы не выполняются.

o Если E имеет значение null, вызывается исключение System.NullReferenceException и дальнейшие этапы не выполняются.

o Создается новый экземпляр типа делегата D. Если для создания нового экземпляра недостаточно памяти, то возникает исключение System.OutOfMemoryException и дальнейшие этапы не выполняются

o Новый экземпляр делегата инициализируется с помощью того же списка вызова, что и экземпляр делегата, предоставленный E.

Список вызова делегата определяется при инициализации делегата и остается неизменным в течение всего срока жизни делегата. Другими словами после создания делегата изменить его целевые вызываемые сущности невозможно. При объединении двух делегатов или удалении одного из другого (§15.1) создается новый делегат. Содержимое существующих делегатов не меняется.

Нельзя создать делегат, который бы ссылался на свойство, индексатор, пользовательский оператор, конструктор экземпляра, деструктор или статический конструктор.

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

delegate double DoubleFunc(double x);

class A
{
DoubleFunc f = new DoubleFunc(Square);

static float Square(float x) {
return x * x;
}

static double Square(double x) {
return x * x;
}
}

поле A.f инициализируется с помощью делегата, который ссылается на второй метод Square, потому что этот метод в точности совпадает по списку формальных параметров и типу возвращаемого значения с DoubleFunc. Если бы второй метод Square отсутствовал, то возникла бы ошибка времени компиляции.

7.5.10.6 Выражения создания анонимных объектов

Выражение_создания_анонимного_объекта используется для создания объекта анонимного типа.

выражение создания анонимного объекта:
new инициализатор анонимного объекта

инициализатор анонимного объекта
{ список деклараторов членовнеобяз }
{ список деклараторов членов , }

список деклараторов членов:
декларатор члена
список деклараторов членов , декларатор члена

декларатор члена:
простое имя
доступ к члену
идентификатор = выражение

Инициализатор анонимного объекта объявляет анонимный тип и возвращает экземпляр этого типа. Анонимный тип — это безымянный тип класса, который наследуется непосредственно от класса object. Члены анонимного типа представляют собой последовательность свойств только для чтения, выводимых из инициализатора анонимного объекта, использованного для создания экземпляра типа. В частности, инициализатор анонимного объекта вида

new { p1 = e1 , p2 = e2 , … pn = en }

объявляет анонимный тип вида

class __Anonymous1
{
private readonly T1 f1 ;
private readonly T2 f2 ;

private readonly Tn fn ;

public __Anonymous1(T1 a1, T2 a2,…, Tn an) {
f1 = a1 ;
f2 = a2 ;

fn = an ;
}

public T1 p1 { get { return f1 ; } }
public T2 p2 { get { return f2 ; } }

public T1 p1 { get { return f1 ; } }

public override bool Equals(object o) { … }
public override int GetHashCode() { … }
}

где каждый Tx является типом соответствующего выражения ex. Выражение, используемое в деклараторе_члена, должно иметь тип. Поэтому если выражение в деклараторе_члена равно null или является анонимной функцией, возникает ошибка времени компиляции. Ошибка времени компиляции также возникает, если выражение имеет небезопасный тип.

Имя анонимного типа автоматически создается компилятором, и на него нельзя ссылаться в тексте программы.

В рамках одной программы два инициализатора, которые задают последовательность свойств с одинаковыми именами и типами времени компиляции в одном порядке, создадут экземпляры одного анонимного типа.

В данном примере

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

присваивание на последней строке допускается, потому что p1 и p2 имеют один анонимный тип.

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

Название декларатора члена можно сократить до простого имени (§7.5.2) или доступа к методу (§7.5.4). Такой способ называется инициализацией проекции и представляет собой удобный способ для объявления присваивания свойству с таким же именем. В частности, деклараторы членов вида

идентификатор выр. идентификатор

в точности совпадают со следующими выражениями соответственно:

идентификатор = идентификатор идентификатор = выр . идентификатор

Таким образом, при инициализации проекции идентификатор позволяет выбирать и значение, и поле или свойство, которому присвоено это значение. С интуитивной точки зрения при инициализации проекции проецируется не только значение, но и имя значения.

Typeof

Оператор typeof используется для получения объекта System.Type для типа.

выражение typeof
typeof ( тип )
typeof ( непривязанное имя типа )
typeof ( void )

непривязанное имя типа:
идентификатор универсальный спецификатор измерениянеобяз
идентификатор :: идентификатор универсальный спецификатор измерениянеобяз
непривязанное имя типа . идентификатор универсальный спецификатор измерениянеобяз

универсальный спецификатор измерения:
< запятыенеобяз >

запятые:
,
запятые ,

Выражение_typeof первого типа состоит из ключевого слова typeof, за которым следует тип в скобках. Результатом выражения этого типа является объект System.Type для указанного типа. Для любого типа существует только один объект System.Type. Это означает, что для типа T всегда выполняет равенство typeof(T) == typeof(T).

Выражение_typeof второго типа состоит из ключевого слова typeof, за которым следует непривязанное_имя_типа в скобках. Непривязанное_имя_типа очень похоже на имя_типа (§3.8) за исключением того, что непривязанное_имя_типа содержит универсальные_спецификаторы_измерения, тогда как имя_типа содержит списки_аргументов_типа. Когда операнд выражения_typeof является последовательностью лексем, соответствующей правилам формирования непривязанного_имени_типа и имени_типа, а именно когда он не содержит ни универсального_спецификатора_измерения, ни списка_аргументов_типа, то последовательность лексем считается именем_типа. Значение непривязанного_имени_типа определяется следующим образом.

· Последовательность лексем преобразуется в имя_типа путем замены каждого универсального_спецификатора_измерения на список_аргументов_типа с таким же числом запятых и ключевым словом object, как и у каждого аргумента_типа.

· Получившееся имя_типа вычисляется, причем все ограничения параметра типа игнорируются.

· Непривязанное_имя_типа разрешается в непривязанный универсальный метод, связанный с результирующим сформированным типом (§4.4.3).

Результатом выражения_typeof является объект System.Type для результирующего непривязанного универсального типа.

Выражение_typeof третьего типа состоит из ключевого слова typeof, за которым следует ключевое слово void в скобках. Результатом выражения этого типа является объект System.Type, обозначающий отсутствие типа. Объект типа, возвращаемый выражением typeof(void), отличается от объекта типа, возвращаемого для любого типа. Этот специальный объект типа удобно использовать в библиотеках классов, которые допускают отражение в методы языка, где для этих методов желательно иметь способ представления типа возвращаемого значения, включая методы, возвращающие void, с помощью экземпляра System.Type.

Оператор typeof можно использовать в параметре типа. Результатом является объект System.Type для типа времени выполнения, который был связан с параметром типа. Оператор typeof также можно использовать в сформированном типе или непривязанном универсальном типе (§4.4.3). Объект System.Type для непривязанного универсального типа отличается от объекта System.Type для типа экземпляра. Тип экземпляра всегда является закрытым сформированным типом во время выполнения, поэтому его объект System.Type зависит от используемых во время выполнения аргументов типа, тогда как у непривязанного универсального типа аргументов типа нет.

В примере

using System;

class X<T>
{
public static void PrintTypes() {
Type[] t = {
typeof(int),
typeof(System.Int32),
typeof(string),
typeof(double[]),
typeof(void),
typeof(T),
typeof(X<T>),
typeof(X<X<T>>),
typeof(X<>)
};
for (int i = 0; i < t.Length; i++) {
Console.WriteLine(t[i]);
}
}
}

class Test
{
static void Main() {
X<int>.PrintTypes();
}
}

получается следующий вывод

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

Обратите внимание, что int и System.Int32 являются одним типом.

Также обратите внимание, что результат выражения typeof(X<>) не зависит от аргумента типа, а результат typeof(X<T>) зависит.

Checked и unchecked

выражение checked: checked ( выражение… выражение unchecked:… Оператор checked…

7.5.13 Выражения значения по умолчанию

Выражение значения по умолчанию используется для получения значения по умолчанию (§5.2) для типа. Обычно выражение значения по умолчанию используется для параметров типа, поскольку может быть неизвестно, имеет ли параметр типа тип значения или ссылочный тип. (Если тип параметра типа отличается от ссылочного, то преобразования из литерала null в параметр типа не существует.)

выражение значения по умолчанию:
default ( тип )

Если тип в выражении_значения_по_умолчанию во время выполнения оказывается ссылочным типом, то результатом выражения является значение null, преобразованное в этот тип. Если тип в выражении_значения_по_умолчанию во время выполнения оказывается типом значения, то результатом выражения является значение типа_значения по умолчанию (§4.1.2).

Выражение_значения_по_умолчанию является константным выражением (§7.18), если тип является ссылочным или параметром типа, который является ссылочным типом (§10.1.5). Кроме того, выражение_значения_по умолчанию является константным выражением, если тип является одним из следующих типов значения: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool или любым типом перечисления.

7.5.14 Выражения анонимного метода

Выражение_анонимного_метода представляет собой один из двух способов определения анонимной функции. Они описываются дальше в разделе §7.14.

7.6 Унарные операторы

Операторы +, -, !, ~, ++, -- и операторы приведения типа называются унарными операторами.

унарное выражение:
основное выражение
+ унарное выражение
- унарное выражение
! унарное выражение
~ унарное выражение
выражение инкремента в префиксе
выражение декремента в префиксе
выражение приведения типа

7.6.1 Унарный оператор «плюс»

Для операции вида +x чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки унарного оператора (§7.2.3). Операнд преобразутся в тип параметра выбранного оператора, а тип результата является типом возвращаемого значения этого оператора. К cтандартным унарным операторам «плюс» относятся:

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

Для каждого из этих операторов результатом является просто значение операнда.

7.6.2 Унарный оператор «минус»

Для операции вида -x чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки унарного оператора (§7.2.3). Операнд преобразутся в тип параметра выбранного оператора, а тип результата является типом возвращаемого значения этого оператора. К cтандартным операторам взятия обратного знака относятся:

· Взятие обратного знака для целых чисел:

int operator –(int x);
long operator –(long x);

Результат вычисляется вычитанием x из нуля. Если значение x является наименьшим представимым значением для типа операнда (−231 для int или −263 для long), то математическая операция взятия обратного знака для не представима в типе операнда. Если это происходит в контексте checked, то возникает исключение System.OverflowException. Если это происходит в контексте unchecked, то результатом является значение операнда и о переполнении не сообщается.

Если операнд оператора взятия обратного знака имеет тип uint, то он преобразуется в тип long и результат имеет тип long. Исключением является правило, которое позволяет записывать значение типа int −2147483648 (−231) в виде литерала десятичного целого числа (§2.4.4.2).

Если операнд оператора взятия обратного знака имеет тип ulong, возникает ошибка времени компиляции. Исключением является правило, которое позволяет записывать значение типа long −9223372036854775808 (−263) в виде литерала десятичного целого числа (§2.4.4.2).

· Взятие обратного знака для чисел с плавающей запятой:

float operator –(float x);
double operator –(double x);

Результатом является значение x с обратным знаком. Если x является NaN, то результат также равен NaN.

· Взятие обратного знака для десятичных чисел:

decimal operator –(decimal x);

Результат вычисляется вычитанием x из нуля. Взятие обратного знака для десятичных чисел эквивалентно использованию унарного оператора «минус» типа System.Decimal.

7.6.3 Оператор логического отрицания

Для операции вида !x чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки унарного оператора (§7.2.3). Операнд преобразутся в тип параметра выбранного оператора, а тип результата является типом возвращаемого значения этого оператора. Существует единственный стандартный оператор логического отрицания:

bool operator !(bool x);

Этот оператор вычисляет логическое отрицание операнда. Если операнд равен true, то результат равен false. Если операнд равен false, то результат равен true.

7.6.4 Оператор побитового дополнения

Для операции вида ~x чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки унарного оператора (§7.2.3). Операнд преобразутся в тип параметра выбранного оператора, а тип результата является типом возвращаемого значения этого оператора. К стандартным операторам побитового дополнения относятся:

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

Для каждого из этих операторов результатов операции является побитовое дополнение x.

Каждый тип перечисления E неявно предоставляет следующий оператор побитового дополнения:

E operator ~(E x);

Результат вычисления ~x, где x является выражением типа перечисления E с базовым типом U, в точности совпадает с результатом вычисления (E)(~(U)x).

7.6.5 Префиксные операторы инкремента и декремента

префиксное выражение инкремента:
++ унарное выражение

префиксное выражение декремента:
-- унарное выражение

Операнд префиксного оператора инкремента или декремента должен быть выражением, которое классифицируется как переменная, доступ к свойству или доступ к индексатору. Результатом операции является значение того же типа, что и операнд.

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

Для выбора конкретной реализации оператора используется разрешение перегрузки унарных операторов (§7.2.3). Стандартные операторы ++ и -- применимы к следующим типам: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal и любым типам перечисления. Стандартный оператор ++ возвращает значение, полученное добавлением 1 к операнду, а стандартный оператор -- возвращает значение, полученное вычитанием 1 из операнда. В контексте checked если результат такого сложения или вычитания выходит за пределы допустимого диапазона для типа результата и результат имеет целый тип или тип перечисления, то возникает исключение System.OverflowException.

Во время выполнения обработка префиксных операций инкремента или декремента в виде ++x или --x включает следующие этапы.

· Если x классифицируется как переменная, то:

o x вычисляется для создания переменной.

o Вызывается выбранный оператор со значением x в качестве аргумента.

o Значение, возвращенное оператором, сохраняется в расположении, предоставленном при вычислении x.

o Значение, возвращенное оператором, становится результатом операции.

· Если x классифицируется как свойство или доступ к индексатору, то:

o Вычисляются выражение экземпляра (если x не имеет тип static) и список аргументов (если x является доступом к экземпляру), связанные с x, и полученные результаты используются при последующих вызовах методов доступа get и set.

o Вызывается метод доступа get для x.

o Вызывается выбранный оператор со значением аргумента, равным возвращенному значению метода доступа get.

o Вызывается метод доступа set для x со значением, возвращенным оператором в качестве своего аргумента value.

o Значение, возвращенное оператором, становится результатом операции.

Операторы ++ и -- также могут использоваться в препозиции (§7.5.9). Результатом операторов x++ и x-- является значение x до операции, тогда как результатом ++x и --x является значение x после операции. В обоих случаях сама переменная x имеет одинаковое значение после операции.

Реализацию оператора ++ или -- можно вызывать в префиксной и постфиксной форме. Для двух этих форм нельзя создать разные реализации операторов.

7.6.6 Выражения приведения типа

Выражение_приведения_типа используется для явного преобразования выражения в данный тип.

выражение приведения типа:
( тип ) унарное выражение

Выражение_приведения_типа вида (T)E, где T является типом, а E —унарным_выражением, выполняет явное преобразование (§6.2) значения E в тип T. Если явное преобразование из E в T отсутствует, то возникает ошибка времени компиляции. Иначе результатом является значение, полученное при явном преобразовании. Результат всегда классифицируется как значение, даже если E обозначает переменную.

Запись выражения_приведения_типа создает некоторую синтаксическую неоднозначность. Например, выражение (x)–y можно интерпретировать как выражение_приведения_типа (приведение–y к типу x) или как аддитивное_выражение с выражением_в_скобках (в котором вычисляется значение x – y).

Для разрешения неоднозначности в выражении_приведения_типа существует следующее правило. Последовательность из одной или нескольких лексем (§2.3.3), заключенная в круглые скобки считается началом выражения_приведения_типа, только если верно одно из следующих условий.

· Последовательность лексем имеет правильную грамматику для типа, но не для выражения.

· Последовательность лексем имеет правильную грамматику для типа, и лексема, непосредственно следующая за закрывающей скобкой, равна «~», «!», «(», идентификатору (§2.4.1), литералу (§2.4.4) или любому ключевому слову (§2.4.3) за исключением as и is.

Термин «правильная грамматика» выше означает только то, что последовательность лексем должна соответствовать конкретному грамматическому выводу. В нем специально не учитывается фактическое значение каких-либо составляющих идентификаторов. Например, если x и y являются идентификаторами, то выражение x.y имеет правильную грамматику для типа, даже если x.y фактически не означает тип.

Из правила снятия неоднозначности следует, что если x и y являются идентификаторами, то (x)y, (x)(y) и (x)(-y) являются выражениями_приведения_типа, а (x)-y не является, даже если x обозначает тип. Однако если x является ключевым словом, которое обозначает стандартный тип (например, int), то все четыре вида выражения являются выражениями_приведения_типа (потому что такое ключевое слово не может само быть выражением).

7.7 Арифметические операторы

Операторы *, /, %, + и– называются арифметическими операторами.

мультипликативное выражение:
унарное выражение
мультипликативное выражение * унарное выражение
мультипликативное выражение / унарное выражение
мультипликативное выражение % унарное выражение

аддитивное выражение:
мультипликативное выражение
аддитивное выражение + мультипликативное выражение
аддитивное выражение – мультипликативное выражение

7.7.1 Оператор произведения

Для операции вида x * y чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

Ниже перечислены стандартные операторы произведения. Все операторы вычисляют произведение x и y.

· Произведение целых чисел:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);

В контексте checked если произведение выходит за пределы диапазона типа результирующего значения, возникает исключение System.OverflowException. В контексте unchecked о переполнениях не сообщается, и все старшие биты, выходящие за пределы диапазона результирующего значения, отбрасываются.

· Произведение чисел с плавающей запятой

float operator *(float x, float y);
double operator *(double x, double y);

Произведение вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых значений, нулей, бесконечных значений и ошибок NaN z является результатом x * y. Если результат слишком велик для целевого типа, то z равно бесконечности. Если результат слишком мал для целевого типа, то z равно нулю.

 

  +y –y +0 –0 +∞ –∞ NaN
+x +z –z +0 –0 +∞ –∞ NaN
–x –z +z –0 +0 –∞ +∞ NaN
+0 +0 –0 +0 –0 NaN NaN NaN
–0 –0 +0 –0 +0 NaN NaN NaN
+∞ +∞ –∞ NaN NaN +∞ –∞ NaN
–∞ –∞ +∞ NaN NaN –∞ +∞ NaN
NaN NaN NaN NaN NaN NaN NaN NaN

 

· Произведение десятичных чисел.

decimal operator *(decimal x, decimal y);

Если результирующее значение слишком велико для представления в формате decimal, то возникает исключение System.OverflowException. Если результирующее значение слишком мало для представления в формате decimal, то результат равен нулю. Масштаб результата до округления равен сумме масштабов двух операндов.

Произведение десятичных чисел эквивалентно использованию оператора произведения типа System.Decimal.

7.7.2 Оператор деления

Для операции вида x / y чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

Ниже перечислены стандартные операторы деления. Все операторы вычисляют частное x и y.

· Деление целых чисел:

int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x, ulong y);

Если значение правого операнда равно нулю, возникает исключение System.DivideByZeroException.

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

Если левый операнд является самым малым представимым значением int или long, а правый операнд равен –1, происходит переполнение. В контексте checked это приводит к возникновению исключения System.ArithmeticException (или его подкласса). В контексте unchecked возникновение исключения System.ArithmeticException (или его подкласс) или выдача сообщения о переполнении (с передачей результирующего значения равным левому операнду) определяется реализацией.

· Деление чисел с плавающей запятой:

float operator /(float x, float y);
double operator /(double x, double y);

Частное вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых значений, нулей, бесконечных значений и ошибок NaN z является результатом x / y. Если результат слишком велик для целевого типа, то z равно бесконечности. Если результат слишком мал для целевого типа, то z равно нулю.

 

  +y –y +0 –0 +∞ –∞ NaN
+x +z –z +∞ –∞ +0 –0 NaN
–x –z +z –∞ +∞ –0 +0 NaN
+0 +0 –0 NaN NaN +0 –0 NaN
–0 –0 +0 NaN NaN –0 +0 NaN
+∞ +∞ –∞ +∞ –∞ NaN NaN NaN
–∞ –∞ +∞ –∞ +∞ NaN NaN NaN
NaN NaN NaN NaN NaN NaN NaN NaN

 

· Деление десятичных чисел:

decimal operator /(decimal x, decimal y);

Если значение правого операнда равно нулю, возникает исключение System.DivideByZeroException. Если результирующее значение слишком велико для представления в формате decimal, то возникает исключение System.OverflowException. Если результирующее значение слишком мало для представления в формате decimal, то результат равен нулю. Масштаб результата равен минимальному масштабу, который позволит сохранить результат равный ближайшему к истинному математическому результату представимому десятичному числу.

Деление десятичных чисел эквивалентно использованию оператора деления типа System.Decimal.

7.7.3 Оператор остатка

Для операции вида x % y чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

Ниже перечислены стандартные операторы остатка. Все операторы вычисляют остаток деления x и y.

· Остаток для целых чисел:

int operator %(int x, int y);
uint operator %(uint x, uint y);
long operator %(long x, long y);
ulong operator %(ulong x, ulong y);

Результатом выражения x % y является значение x – (x / y) * y. Если y равно нулю, возникает исключение System.DivideByZeroException.

Если левый операнд является минимальным значением типа int или long, а правый операнд равен -1, то возникает исключение System.OverflowException. Если x / y не порождает исключения, x % y также не порождает исключения.

· Остаток для чисел с плавающей запятой:

float operator %(float x, float y);
double operator %(double x, double y);

В следующей таблице приведены результаты всех возможных комбинаций ненулевых значений, нулей, бесконечных значений и ошибок NaN z является результатом x % y и вычисляется как x – n * y, где n является максимальным возможным целым числом, меньшим или равным x / y. Этот метод вычисления остатка аналогичен методу, используемому для целых операндов, но отличается от определения по стандарту IEEE 754 (в котором n является целым числом, ближайшим к x / y).

 

  +y –y +0 –0 +∞ –∞ NaN
+x +z +z NaN NaN x x NaN
–x –z –z NaN NaN –x –x NaN
+0 +0 +0 NaN NaN +0 +0 NaN
–0 –0 –0 NaN NaN –0 –0 NaN
+∞ NaN NaN NaN NaN NaN NaN NaN
–∞ NaN NaN NaN NaN NaN NaN NaN
NaN NaN NaN NaN NaN NaN NaN NaN

 

· Остаток для десятичных чисел:

decimal operator %(decimal x, decimal y);

Если значение правого операнда равно нулю, возникает исключение System.DivideByZeroException. Масштаб результат до округления равен большему масштабу из двух операндов, а знак результата (если он не равен нулю) равен знаку x.

Получение остатка для десятичных чисел эквивалентно использованию оператора остатка типа System.Decimal.

7.7.4 Оператор сложения

Для операции вида x + y чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

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

· Сложение целых чисел:

int operator +(int x, int y);
uint operator +(uint x, uint y);
long operator +(long x, long y);
ulong operator +(ulong x, ulong y);

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

· Сложение чисел с плавающей запятой:

float operator +(float x, float y);
double operator +(double x, double y);

Сумма вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых конечных значений, нулей, бесконечных значений и ошибок NaN В таблице x и y являются ненулевыми конечными значениями, а z является результатом x + y. Если x и y имеют одинаковую величину, но противоположные знаки, то z равен положительному нулю. Если результат x + y слишком велик для представления в целевом типе, то z является бесконечным значением с таким же знаком, как и у x + y.

 

  y +0 –0 +∞ –∞ NaN
x z x x +∞ –∞ NaN
+0 y +0 +0 +∞ –∞ NaN
–0 y +0 –0 +∞ –∞ NaN
+∞ +∞ +∞ +∞ +∞ NaN NaN
–∞ –∞ –∞ –∞ NaN –∞ NaN
NaN NaN NaN NaN NaN NaN NaN

 

· Сложение десятичных чисел:

decimal operator +(decimal x, decimal y);

Если результирующее значение слишком велико для представления в формате decimal, то возникает исключение System.OverflowException. Масштаб результата до округления равен большему из масштабов двух операндов.

Получение суммы десятичных чисел эквивалентно использованию оператора сложения типа System.Decimal.

· Сложение элементов перечисления. В каждом типе перечисления неявно предоставляются следующие стандартные операторы, где E является типом перечисления, а U является базовым типом E:

E operator +(E x, U y);
E operator +(U x, E y);

Операторы вычисляются в точности как (E)((U)x + (U)y).

· Сцепление строк:

string operator +(string x, string y);
string operator +(string x, object y);
string operator +(object x, string y);

Бинарный оператор + выполняет сцепление строк, когда один или оба операнда имеют тип string. Если один операнд при сцеплении строк равен null, то подставляется пустая строка. Иначе любой нестроковый аргумент преобразуется в свое строковое представление путем вызова виртуального метода ToString, наследуемого от типа bject. Если метод ToString возвращает null, то подставляется пустая строка.

using System;

class Test
{
static void Main() {
string s = null;
Console.WriteLine("s = >" + s + "<"); // отображается s = ><
int i = 1;
Console.WriteLine("i = " + i); // отображается i = 1
float f = 1.2300E+15F;
Console.WriteLine("f = " + f); // отображается f = 1.23E+15
decimal d = 2.900m;
Console.WriteLine("d = " + d); // отображается d = 2.900
}
}

Результатом оператора сцепления строк является строка, состоящая из символов левого операнда, за которыми следуют символы правого операнда. Оператор сцепления строк никогда не возвращает значение null. При отсутствии памяти для размещения результирующей строки может возникать исключение System.OutOfMemoryException.

· Комбинация делегатов. В каждом типе делегата неявно предоставляется следующий стандартный оператор, где D имеет тип делегата:

D operator +(D x, D y);

Бинарный оператор + выполняет комбинацию делегатов, когда оба операнда имеют некоторый тип делегата D. (Если операнды имеют разные типы делегатов, возникает ошибка времени компиляции.) Если первый операнд равен null, результат операции равен значению второго операнда (даже если оно также равно null). Иначе, если второй операнд равен null, то результатом операции является значение первого операнда. Иначе результатом операции является новый экземпляр делегата, который при вызове вызывает первый операнд, а затем второй операнд. Примеры комбинации операндов см. в разделах §7.7.5 и §15.4. Поскольку System.Delegate не является типом делегата, для него оператор + не определен.

7.7.5 Оператор вычитания

Для операции вида x – y, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

Ниже перечислены стандартные операторы вычитания. Все операторы выполняют вычитание y из x.

· Вычитание целых чисел:

int operator –(int x, int y);
uint operator –(uint x, uint y);
long operator –(long x, long y);
ulong operator –(ulong x, ulong y);

В контексте checked если разность выходит за пределы диапазона типа результирующего значения, возникает исключение System.OverflowException. В контексте unchecked о переполнениях не сообщается, и все старшие биты, выходящие за пределы диапазона результирующего значения, отбрасываются.

· Вычитание чисел с плавающей запятой:

float operator –(float x, float y);
double operator –(double x, double y);

Разность вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых конечных значений, нулей, бесконечных значений и ошибок NaN В таблице x и y являются ненулевыми конечными значениями, а z является результатом x - y. Если x и y равны, z равно положительному нулю. Если результат x - y слишком велик для представления в целевом типе, то z является бесконечным значением с таким же знаком, как и у x - y.

 

  y +0 –0 +∞ –∞ NaN
x z x x –∞ +∞ NaN
+0 –y +0 +0 –∞ +∞ NaN
–0 –y –0 +0 –∞ +∞ NaN
+∞ +∞ +∞ +∞ NaN +∞ NaN
–∞ –∞ –∞ –∞ –∞ NaN NaN
NaN NaN NaN NaN NaN NaN NaN

 

· Вычитание десятичных чисел.

decimal operator –(decimal x, decimal y);

Если результирующее значение слишком велико для представления в формате decimal, то возникает исключение System.OverflowException. Масштаб результата до округления равен большему из масштабов двух операндов.

Вычитание десятичных чисел эквивалентно использованию оператора вычитания типа System.Decimal.

· Вычитание элементов перечисления. В каждом типе перечисления неявно предоставляется следующий стандартный оператор, где E является типом перечисления, а U является базовым типом E:

U operator –(E x, E y);

Этот оператор вычисляется в точности как (U)((U)x – (U)y). Другими словами, оператор вычисляет разность между порядковыми значениями x и y, а типом результата является базовый тип перечисления.

E operator –(E x, U y);

Этот оператор вычисляется в точности как (E)((U)x – y). Другими словами, оператор вычитает значение из базового типа перечисления, выдавая значение перечисления.

· Удаление делегатов. В каждом типе делегата неявно предоставляется следующий стандартный оператор, где D имеет тип делегата:

D operator –(D x, D y);

Бинарный оператор - выполняет удаление делегатов, когда оба операнда имеют некоторый тип делегата D. Если операнды имеют разные типы делегатов, возникает ошибка времени компиляции. Если первый операнд равен null, то результатом операции является null. Иначе, если второй операнд равен null, то результатом операции является значение первого операнда. Иначе оба операнда представляют списки вызова (§15.1) с одной или несколькими записями, а результатом является новый список вызова, состоящий из списка первого операнда, из которого удалены записи второго операнда, при условии что список второго операнда является соответствующим смежным подсписком списка первого операнда. (Для определения равенства подсписков выполняется сравнение соответствующих записей, как и для оператора равенства делегатов (§7.9.8).) Иначе результатом является значение левого операнда. В ходе этого процессе списки операндов не меняются. Если список второго операнда соответствует нескольким подспискам последовательных записей в списке первого операнда, то самый правый совпадающий подписок последовательных записей удаляется. Если после удаления получается пустой список, то результатом является null. Например:

delegate void D(int x);

class C
{
public static void M1(int i) { /* … */ }
public static void M2(int i) { /* … */ }
}

class Test
{
static void Main() {
D cd1 = new D(C.M1);
D cd2 = new D(C.M2);
D cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd1; // => M1 + M2 + M2

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd1 + cd2; // => M2 + M1

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd2 + cd2; // => M1 + M1

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd2 + cd1; // => M1 + M2

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd1 + cd1; // => M1 + M2 + M2 + M1
}
}

7.8 Операторы сдвига

Операторы << и >> используются для выполнения операций побитового сдвига.

выражение сдвига:
аддитивное выражение
выражение сдвига << аддитивное выражение
выражение сдвига сдвиг вправо аддитивное выражение

Для операции вида x << число или x >> число, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

При объявлении перегруженного оператора сдвига тип первого операнда всегда должен быть классом или структурой, в которой находится объявление оператора, а второй операнд должен иметь тип int.

Ниже перечислены стандартные операторы сдвига.

· Сдвиг влево:

int operator <<(int x, int count);
uint operator <<(uint x, int count);
long operator <<(long x, int count);
ulong operator <<(ulong x, int count);

Оператор << выполняет сдвиг значения x влево на определенное число битов и вычисляется, как указано ниже.

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

· Сдвиг вправо

int operator >>(int x, int count);
uint operator >>(uint x, int count);
long operator >>(long x, int count);
ulong operator >>(ulong x, int count);

Оператор >> выполняет сдвиг значения x вправо на определенное число битов и вычисляется, как указано ниже.

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

Если x имеет тип uint или ulong, младшие биты x отбрасываются, оставшиеся биты сдвигаются вправо, а пустые позиции старших битов заполняются нулями.

Для стандартных операторов число сдвигаемых битов вычисляется следующим образом.

· Если x имеет тип int или uint, размер сдвига задается пятью младшими битами числа. Другими словами, величина сдвига вычисляется от значения число & 0x1F.

· Если x имеет тип long или ulong, величина сдвига задается шестью младшими битами числа. Другими словами, величина сдвига вычисляется от значения число & 0x3F.

Если результирующая величина сдвига равна нулю, то операторы сдвига просто возвращают значение x.

Операции сдвига никогда не вызывают переполнения и дают одинаковые результаты в контекстах checked и unchecked.

Если левый операнд оператора >> имеет целый тип со знаком, оператор выполняет арифметический сдвиг вправо, когда значение самого важного бита (бита знака) операнда распространяется на пустые позиции старших битов. Если левый операнд оператора >> имеет целый тип без знака, оператор выполняет логический сдвиг вправо, когда пустые позиции старших битов всегда заполняются нулями. Для выполнения обратной операции выведенной из типа операнда можно использовать явное приведение типов. Например, если x является переменной типа int, то операция unchecked((int)((uint)x >> y)) выполняет логический сдвиг x вправо.

7.9 Операторы отношения и проверки типа

Операторы ==, !=, <, >, <=, >=, is и as называются операторами отношения и проверки типа.

выражение отношения:
выражение сдвига
выражение отношения < выражение сдвига
выражение отношения > выражение сдвига
выражение отношения <= выражение сдвига
выражение отношения >= выражение сдвига
выражение отношения is тип
выражение отношения as тип

выражение равенства:
выражение отношения
выражение равенства == выражение отношения
выражение равенства != выражение отношения

Оператор is описывается в разделе §7.9.10, а оператор as — в разделе §7.9.11.

Операторы ==, !=, <, >, <= и >= являются операторами сравнения. Для операции вида x op y, где op является оператором сравнения, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

Стандартные операторы сравнения описываются в следующих разделах. Все стандартные операторы сравнения возвращают результат с типом bool, как показано в следующей таблице.

 

Операция Результат
x == y true, если x равно y, в противном случае false
x != y true, если x не равно y, в противном случае false
x < y true, если x меньше y, в противном случае false
x > y true, если x больше y, в противном случае false
x <= y true, если x меньше или равно y, в противном случае false
x >= y true, если x больше или равно y, в противном случае false

 

7.9.1 Операторы сравнения целых чисел

К стандартным операторам сравнения целых чисел относятся:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Каждый из этих операторов сравнивает числовые значения двух целых операндов и возвращает значение bool, которое указывает, является ли соответствующее отношение истинным или ложным.

7.9.2 Операторы сравнения чисел с плавающей запятой

К стандартным операторам сравнения чисел сплавающей запятой относятся:

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

Эти операторы сравнивают операнды в соответствии с правилами из стандарта IEEE 754.

· Если какой-либо из операторов не является числом, то результатом всех операторов является false, за исключением оператора!=, для которого результатом является true. Для любых двух операндов x != y всегда дает такой же результат, что и !(x == y). Однако когда один или оба операнда не являются числами, операторы <, >, <=, и >= не дают такие же результаты, как логическое отрицание обратного оператора. Например, если x или y не является числом, то x < y имеет значение false, однако!(x >= y) имеет значение true.

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

–∞ < –макс. < ... < –мин. < –0.0 == +0.0 < +мин. < ... < +макс. < +∞

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

o Отрицательные и положительные нулевые значения считаются равными.

o Отрицательное бесконечное значение считается меньше всех остальных значений, но равным другому отрицательному бесконечному значению.

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

7.9.3 Операторы сравнения десятичных чисел

К стандартным операторам сравнения десятичных чисел относятся:

bool operator ==(decimal x, decimal y);

bool operator !=(decimal x, decimal y);

bool operator <(decimal x, decimal y);

bool operator >(decimal x, decimal y);

bool operator <=(decimal x, decimal y);

bool operator >=(decimal x, decimal y);

Каждый из этих операторов сравнивает числовые значения двух десятичных операндов и возвращает значение bool, которое указывает, является ли соответствующее отношение истинным или ложным. Каждый оператор сравнения десятичных чисел эквивалентен соответствующему оператору отношения или равенства типа System.Decimal.

7.9.4 Логические операторы равенства

К стандартным логическим операторам равенства относятся:

bool operator ==(bool x, bool y);

bool operator !=(bool x, bool y);

Результат == равен true, если оба значения x и y имеют значение true или оба имеют значение false. В противном случае, результат равен false.

Результат !== равен false, если оба значения x и y имеют значение true или оба имеют значение false. В противном случае результат равен true. Если операнды имеют тип bool, оператор != дает такой же результат, как и оператор ^.

7.9.5 Операторы сравнения значений перечисления

Каждый тип перечисления неявно предоставляет следующие стандартные операторы сравнения:

bool operator ==(E x, E y);

bool operator !=(E x, E y);

bool operator <(E x, E y);

bool operator >(E x, E y);

bool operator <=(E x, E y);

bool operator >=(E x, E y);

Результат вычисления x op y, где x и y являются выражениями с типом перечисления E базового типа U, а op является одним из операторов сравнения, в точности равен значению ((U)x) op ((U)y). Другими словами, операторы сравнения значений типа перечисления просто сравнивают базовые целые значения двух операндов.

7.9.6 Операторы равенства значений ссылочного типа

К стандартным операторам равенства значений ссылочного типа относятся:

bool operator ==(object x, object y);

bool operator !=(object x, object y);

Операторы возвращают результат сравнения двух ссылок на идентичность.

Поскольку стандартные операторы равенства значений ссылочного типа принимают операнды типов object, они применимы ко всем типам, в которых не объявляются применимые члены operator == и operator !=. И наоборот, любые применимые пользовательские операторы равенства скрывают стандартные операторы равенства значений ссылочного типа.

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

· Оба операнда являются значениями ссылочного_типа или значением null. Кроме того, из типа одного операнда в тип другого существует стандартное неявное преобразование (§6.3.1).

· Один операнд имеет значение типа T, где T является параметром_типа, а второй операнд имеет значение null. Кроме того, у T нет ограничений типа значения.

Если не выполняется какое-либо из этих требований, возникает ошибка времени компиляции. Основные следствия из этих правил таковы.

· Если известно, что во время компиляции две ссылки отличаются, то использование стандартных операторов равенства значений ссылочного типа для их сравнения вызывает ошибку времени компиляции. Например, если типы операндов во время выполнения — это типы классов A и B и если ни A, ни B не является производным от другого класса, то эти два операнда не смогут ссылаться на один объект. Следовательно, операция вызовет ошибку времени компиляции.

· Стандартные операторы равенства значений ссылочного типа не позволяют сравнивать операнды с типом значения. Поэтому нельзя сравнивать значения типа структуры, если только в этом типе не объявляются собственные операторы сравнения.

· Стандартные операторы равенства значений ссылочного типа никогда не вызывают выполнение операций упаковки для своих операндов. Выполнение таких операций упаковки будет бессмысленным, поскольку ссылки на создаваемые упакованные экземпляры обязательно будут отличаться от всех остальных ссылок.

· Если операнд с типом параметра типа T сравнивается со значением null и тип T во время выполнения является типом значения, то результат сравнения равен false.

В следующем примере проверяется, имеет ли аргумент типа параметра типа без ограничений значение null.

class C<T>
{
void F(T x) {
if (x == null) throw new ArgumentNullException();
...
}
}

Конструкция x == null разрешена, даже если T может представлять тип значения, и результат просто равен false, когда T имеет тип значения.

Для операции вида x == y или x != y если существует применимый operator == или operator !=, то по правилам разрешения перегрузки операторов (§7.2.4) будет выбран этот оператор вместо стандартного оператора равенства значений ссылочного типа. Однако всегда можно выбрать стандартный оператор равенства значений ссылочного типа, выполнив явное приведение одного или обоих операндов к типу object. В примере

using System;

class Test
{
static void Main() {
string s = "Test";
string t = string.Copy(s);
Console.WriteLine(s == t);
Console.WriteLine((object)s == t);
Console.WriteLine(s == (object)t);
Console.WriteLine((object)s == (object)t);
}
}

получается вывод

True
False
False
False

Переменные s и t относятся к разным экземплярам string, содержащим одинаковые символы Первое сравнение дает значение True, потому что когда оба операнда имеют тип string выбирается стандартный оператор равенства значений ссылочного типа (§7.9.7). В оставшихся операциях сравнения получается значение False, потому что когда один или оба операнда имеют тип object выбирается стандартный оператор равенства значений ссылочного типа.

Обратите внимание, что указанная процедура не применима для типов значения. В примере

class Test
{
static void Main() {
int i = 123;
int j = 123;
System.Console.WriteLine((object)i == (object)j);
}
}

выдается результат False, потому что при приведении типов создаются ссылки на два разных экземпляра упакованных значений int.

7.9.7 Операторы равенства строк

К стандартным операторам равенства строк относятся:

bool operator ==(string x, string y);

bool operator !=(string x, string y);

Два значения типа string считаются равными, когда истинно одно из следующих условий:

· оба значения равны null;

· оба значения являются непустыми ссылками на экземпляры строк с одинаковой длиной и идентичными символами в каждой позиции.

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

7.9.8 Операторы равенства делегатов

Каждый тип делегата неявно предоставляет следующие стандартные операторы сравнения:

bool operator ==(System.Delegate x, System.Delegate y);

bool operator !=(System.Delegate x, System.Delegate y);

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

· Если один из экземпляров делегата равен null, то они равны, только они оба равны null.

· Если делегаты имеют разный тип во время выполнения, то они не могут быть равны.

· Если у обоих экземпляров делегата есть список вызова (§15.1), то они равны, только если их списки вызова имеют одну длину и каждая запись в списке вызова одного делегата равна (в соответствии с определением ниже) соответствующей записи по порядку в списке вызова другого делегата.

Равенство записей списка вызова определяется следующими правилами.

· Если две записи списка вызова ссылаются на один статический метод, то записи равны.

· Если две записи списка вызова ссылаются на один нестатический метод одного целевого объекта (определяется операторами равенства ссылочных значений), то записи равны.

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

Null

Операторы == и!= допускают, что один операнд был значением обнуляемого типа, а второй литералом null, даже если для операции не существует стандартного или пользовательского оператора (обычного или с нулификацией).

Для операции одного из видов

x == null null == x x != null null != x

где x является выражением обнуляемого типа, если при разрешении перегрузки операторов (§7.2.4) не удается найти применимый оператор, то результат вычисляется на основании свойства HasValue для x. В частности, первые два вида операции преобразуются в!x.HasValue, а два последних типа преобразуются в x.HasValue.

Is

Оператор is используется для динамической проверки, можно ли сравнивать тип объекта во время выполнения с указанным типом. Результат операции E is T, где E — это выражение, а T — тип, является логическим значением, которое указывает, можно ли провести преобразование E в тип T с помощью преобразования ссылки, преобразования упаковки или преобразования отмены упаковки. После замены всех параметров типа на аргументы типа операция вычисляется следующим образом.

· Если E является анонимной функцией, возникает ошибка времени компиляции

· Если E является группой методов или литералом null или если E имеет ссылочный или обнуляемый и значение E равно null, то результат равен false.

· Иначе пусть D обозначает динамический тип E следующим образом.

o Если E имеет ссылочный тип, D является типом времени выполнения для экземпляра, на который ссылается E.

o Если E имеет обнуляемый тип, D является базовым типом этого обнуляемого типа.

o Если E имеет необнуляемый тип значения, D имеет тип E.

· Результат операции зависит от D и T следующим образом.

o Если T имеет ссылочный тип, то результат равен true, если D и T имеют одинаковый тип, если D имеет ссылочный тип и существует неявное преобразование ссылки из D в T или если D имеет тип значения и существует преобразование упаковки из D в T.

o Если T имеет обнуляемый тип, результат равен true, если D является базовым типом T.

o Если T является необнуляемым типом значения, результат равен true, если T и D имеют одинаковый тип

o Иначе результат равен false.

Обратите внимание, что пользовательские преобразования в операторе is не учитываются.

As

Оператор as используется для явного преобразования значения в указанный ссылочный тип или обнуляемый тип. В отличие от выражения приведения типа (§7.6.6) оператор as никогда не вызывает исключения. Вместо этого, если указанное преобразование невозможно, возвращается значение null.

В операции вида «E as T» E должно быть выражением, а T должен быть ссылочным типом, параметром типа, который является ссылочным типом, или обнуляемым типом. Кроме того, должно выполняться по крайней мере одно из следующих условий. В противном случае будет возникать ошибка времени компиляции.

· Из типа E в T существует преобразование идентификатора (§6.1.1), неявное преобразование ссылочного типа (§6.1.6), преобразование упаковки (§6.1.7), явное преобразование ссылочного типа (§6.2.4) или преобразование отмены упаковки (§6.2.5).

· E или T имеет открытый тип.

· E является литералом null.

Операция E as T дает такой же результат, как и

E is T ? (T)(E) : (T)null

· за исключением того, что E вычисляется только один раз. Компилятор может проводить оптимизацию операции E as T, чтобы при ее обработке выполнялась только одна динамическая проверка типа в отличие от двух динамических проверок типа в расширении выше.

Обратите внимание, что некоторые преобразования, например пользовательские преобразования нельзя использовать с оператором as и вместо них следует использовать выражения приведения типа.

В данном примере

class X
{

public string F(object o) {
return o as string; // OK, строка имеет ссылочный тип
}

public T G<T>(object o) where T: Attribute {
return o as T; // Нормально, T имеет ограничение класса
}

public U H<U>(object o) {
return o as U; // Ошибка, U не имеет ограничений
}
}

параметр T для G имеет ссылочный тип, поскольку у него есть ограничение класса. Напротив, параметр типа U для H не имеет ссылочного типа, поэтому использование оператора as в H запрещено.

7.10 Логические операторы

Операторы &, ^ и | называются логическими операторами.

выражение И:
выражение равенства
выражение И & выражение равенства

выражение исключающего ИЛИ:
выражение И
выражение исключающего ИЛИ ^ выражение И

выражение включающего ИЛИ:
выражение исключающего ИЛИ
выражение включающего ИЛИ | выражение исключающего ИЛИ

Для операции вида x op y, где op является одним из логических операторов, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки (§7.2.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.

Стандартные логические операторы описываются в следующих разделах.

7.10.1 Логические операторы для целых чисел

К стандартным логическим операторам для целых чисел относятся:

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

Оператор & выполняет логическую побитовую операцию AND для двух операндов, оператор | выполняет логическую побитовую операцию OR для двух операндов, а оператор ^ выполняет логическую побитовую операцию исключающего OR для двух операндов. Эти операции не порождают переполнения.

7.10.2 Логические операторы для перечислений

Каждый тип перечисления E неявно предоставляет следующие стандартные логические операторы:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

Результат вычисления x op y, где x и y являются выражениями с типом перечисления E базового типа U, а op является одним из логических операторов, в точности равен значению (E)((U)x op (U)y). Другими словами, логические операторы для перечислений просто выполняют логические операции над базовым типом двух операндов.

7.10.3 Логические операторы

К стандартным логическим операторам относятся:

bool operator &(bool x, bool y);

bool operator |(bool x, bool y);

bool operator ^(bool x, bool y);

Результат x & y равен true, если оба операнда x и y равны true. В противном случае, результат равен false.

Результат x | y равен true, если хотя бы один операнд x или y равен true. В противном случае, результат равен false.

Результат x ^ y равен true, если x равен true, а y равен false, либо если x равен false, а y равен true. В противном случае результат равен false. Когда операнды имеют тип bool, оператор ^ дает такой же результат, как и оператор !=.

7.10.4 Обнуляемые логические операторы

Обнуляемый логический тип bool? может представлять три значения: true, false и null, и по сути аналогичен типу из трех значений, используемому в логических выражениях в SQL. Чтобы обеспечить согласованность результатов операторов & и | с операндами типа bool? с троичной логикой SQL, предоставляются следующие стандартные операторы:

bool? operator &(bool? x, bool? y);

bool? operator |(bool? x, bool? y);

В следующей таблице перечисляются результаты данных операторов для всех комбинаций значений true, false и null.

 

x y x & y x | y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

 

7.11 Условные логические операторы

Операторы && и || называются условными логическими операторами. Они также называются логическими операторами «краткой записи».

условное выражение И:
выражение включающего ИЛИ
условное выражение И && выражение включающего ИЛИ

условное выражение ИЛИ:
условное выражение И
условное выражение ИЛИ || условное выражение И

Операторы && и || являются условными версиями операторов & и |.

· Операция x && y соответствует операции x & y, за исключением того, что y вычисляется, только если x не равен false.

· Операция x || y соответствует операции x | y, за исключением того, что y вычисляется, только если x не равен true.

Операция вида x && y или x || y обрабатывается с применением разрешения перегрузки (§7.2.4), как если бы операция записывалась в виде x & y или x | y. Тогда

· Если при разрешении перегрузки не удается найти один наилучший оператор или выбирается один из стандартных логических операторов для целых чисел, то возникает ошибка времени компиляции.

· Иначе, если выбранный оператор является одним из стандартных логических операторов (§7.10.3) или обнуляемых логических операторов (§7.10.4), то операция выполняется, как описано в разделе §7.11.1.

· Иначе выбранный оператор является пользовательским оператором, и операция выполняется, как описано в разделе §7.11.2.

Напрямую перегружать условные логические операторы нельзя. Однако поскольку условные логические операторы обрабатываются через обычные логические операторы, перегрузки обычных логических операторов с определенными ограничениями также считаются перегрузками условных логических операторов. Это описывается дальше в разделе §7.11.2.

7.11.1 Логические условные операторы

Если операнды && или || имеют тип bool или типы, в которых не определены применимые операторы operator & или operator |, но определены неявные преобразования в bool, то операция выполняется следующим образом.

· Операция x && y вычисляется как x ? y : false. Другими словами, сначала вычисляется x и затем преобразуется в тип bool. Затем, если x равен true, y вычисляется и преобразуется в тип bool и становится результатом операции. Иначе результат операции равен false.

· Операция x || y вычисляется как x ? true : y. Другими словами, сначала вычисляется x и затем преобразуется в тип bool. Затем, если x равно true, результат операции равен true. Иначе y вычисляется и преобразуется в тип bool и становится результатом операции.

7.11.2 Пользовательские условные логические операторы

Если операнды && или || имеют типы, в которых объявляются применимые пользовательские операторы operator & или operator |, то должны выполняться оба следующих условия, где T является типом, в котором объявляется выбранный оператор.

· Типом возвращаемого значения и типом каждого параметра выбранного оператора должен быть T. Другими словами, оператор должен вычислять операцию логического AND или логического OR для двух операндов типа T и должен возвращать результат типа T.

· T должен содержать объявления operator true и operator false.

Если какое-либо из этих условий не выполняется, возникает ошибка времени выполнения. Иначе операция && или || вычисляется путем объединения пользовательских операторов operator true и operator false с выбранным пользовательским оператором:

· Операция x && y вычисляется как T.false(x) ? x : T.&(x, y), где T.false(x) является вызовом оператора operator false, объявленного в T, а T.&(x, y) является вызовом выбранного оператора &. Другими словами, сначала вычисляется x и для результата вызывается оператор operator false, чтобы определить, имеет ли x значение false. Затем, если x имеет значение false, результатом операции становится значение, ранее вычисленное для x. Иначе вычисляется y и выбранный оператор operator & вызывается для ранее вычисленного значения для x и вычисленного значения y, чтобы получить результат операции.

· Операция x || y вычисляется как T.true(x) ? x : T.|(x, y), где T.true(x) является вызовом оператора operator true, объявленного в T, а T.|(x, y) является вызовом выбранного оператора |. Другими словами, сначала вычисляется x и для результата вызывается оператор operator true, чтобы определить, имеет ли x значение true. Затем, если x имеет значение true, результатом операции становится значение, ранее вычисленное для x. Иначе вычисляется y и выбранный оператор operator | вызывается для ранее вычисленного значения для x и вычисленного значения y, чтобы получить результат операции.

В обеих этих операциях выражение в x вычисляется только один раз, а выражение в y либо не вычисляется, либо вычисляется ровно один раз.

Пример типа, в котором реализуется оператор operator true и operator false, см. в разделе §11.4.2.

Null

Оператор?? называется оператором слияния с null.

выражение слияния с null:
условное выражение ИЛИ
условное выражение ИЛИ || выражение слияния с null

В выражении слияния с null вида a ?? b требуется, чтобы a имело обнуляемый тип или ссылочный тип. Если a не равно null, то результатом a ?? b является a, в противном случае результатом является b. В операции вычисление b происходит, только если a равно null.

Оператор слияния с null имеет правую ассоциативность, что означает, что операции группируются справа налево. Например, выражение вида a ?? b ?? c вычисляется как a ?? (b ?? c). В общем случае выражение вида E1 ?? E2 ?? ... ?? EN возвращает первый операнд, не равный null, или null, если все операнды равны null.

Тип выражения a ?? b зависит от того, какие неявные преобразования доступны между типами операндов. В порядке предпочтения тип a ?? b равен A0, A или B, где A имеет тип a, B имеет тип b (при условии, что у b есть тип), а A0 является базовым типом A, если A является обнуляемым типом, либо равен типу A в противном случае. В частности, a ?? b обрабатывается следующим образом.

· Если A не является обнуляемым типом или ссылочным типом, возникает ошибка времени компиляции.

· Если A является обнуляемым типом и существует неявное преобразование из b в A0, то типом результата будет A0. Во время выполнения сначала вычисляется a. Если a не равно null, для a выполняется снятие упаковки до типа A0 и это становится результатом. Иначе вычисляется b и преобразуется в тип A0 и это становится результатом.

· Иначе, если существует неявное преобразование из b в A, то тип результата будет A. Во время выполнения сначала вычисляется a. Если a не равно null, a становится результатом. Иначе вычисляется b и преобразуется в тип A и это становится результатом.

· Иначе, если b имеет тип B и существует неявное преобразование из A0 в B, то типом результата будет B. Во время выполнения сначала вычисляется a. Если a не равно null, для a выполняется снятие упаковки до типа A0 (если только A и A0 не являются одним типом), оно преобразуется в тип B и это становится результатом. Иначе вычисляется b и это становится результатом.

· Иначе a и b являются несовместимыми и возникает ошибка времени компиляции.

7.13 Условный оператор

Оператор ?: называется условным оператором. Иногда его также называют тернарным оператором.

условное выражение:
выражение слияния с null
выражение слияния с null ? выражение : выражение

В условном выражении вида b ? x : y сначала вычисляется условие b. Затем, если b равно true, вычисляется x и это становится результатом операции. Иначе вычисляется y и это становится результатом операции. В условном выражении никогда не выполняется вычисление и x, и y.

Условный оператор имеет правую ассоциативность, что означает, что операции группируются справа налево. Например, выражение вида a ? b : c ? d : e вычисляется как a ? b : (c ? d : e).

Первый операнд оператора?: должен быть выражением с типом, который можно неявно преобразовать в тип bool, или выражением типа, в котором реализован оператор operator true. Если не выполняется ни одно из этих требований, то возникает ошибка времени компиляции.

Второй и третий операнды оператора?: задают тип условного выражения. Пусть X и Y являются типами второго и третьего операндов. Тогда

· Если X и Y являются одним типом, то он является типом условного выражения.

· Иначе, если из X в Y существует неявное преобразование (§6.1), а из Y в X не существует, то типом условного выражения является Y.

· Иначе, если из Y в X существует неявное преобразование (§6.1), а из X в Y не существует, то типом условного выражения является X.

· Иначе тип выражения определить нельзя, и возникает ошибка времени компиляции.

Во время выполнения обработка условного выражения вида b ? x : y включает следующие этапы.

· Сначала вычисляется b и определяется логическое значение b:

o Если существует неявное преобразование из типа b в bool, то для получения логического значения выполняется это неявное преобразование.

o Иначе для получения логического значения вызывается оператор operator true, определенный в типе b.

· Если полученное на предыдущем этапе логическое значение равно true, то x вычисляется и преобразуется в тип условного выражения и это становится результатом условного выражения.

· Иначе y вычисляется и преобразуется в тип условного выражения и это становится результатом условного выражения.

7.14 Выражения анонимных функций

Анонимная функция— это выражение, представляющее собой встроенное определение метода. Анонимная функция не имеет значения сама по себе, но может быть преобразована в совместимый тип делегата или дерева выражения. Преобразование анонимной функции зависит от целевого типа преобразования. Если это тип делегата, то результатом преобразования является значение делегата, ссылающееся на метод, определяемый анонимной функцией Если это тип дерева выражения, то результатом преобразования является дерево выражения, которое представляет структуру метода в виде структуры объекта.

По историческим причинам существует две синтаксические разновидности анонимных функций, а именно: лямбда_выражения и выражения_анонимных_методов. Практически для любых целей лямбда_выражения более конкретны и точны, чем выражения_анонимных_методов, которые остаются в языке в целях обратной совместимости.

лямбда-выражение:
подпись анонимной функции => тело анонимной функции

выражение анонимного метода:
delegate явная подпись анонимной функциинеобяз блок

подпись анонимной функции:
явная подпись анонимной функции
неявная подпись анонимной функции

явная подпись анонимной функции:
( явный список параметров анонимной функциинеобяз )

явный список параметров анонимной функции
явный параметр анонимной функции
явный список параметров анонимной функции , явный параметр анонимной функции

явный параметр анонимной функции:
модификатор параметра анонимной функциинеобяз тип идентификатор

модификатор параметра анонимной функции:
ref
out

неявная подпись анонимной функции:
( неявный список параметров анонимной функциинеобяз )
неявный параметр анонимной функции

неявный список параметров анонимной функции
неявный параметр анонимной функции
неявный список параметров анонимной функции , неявный параметр анонимной функции

неявный параметр анонимной функции:
идентификатор

тело анонимной функции:
выражение
блок

Оператор => имеет такой же приоритет, как и присваивание (=) и обладает правой ассоциативностью.

Тип параметров анонимной функции в виде лямбда_выражения может задаваться явно или неявно. В списке явно типизированных параметров тип каждого параметра указывается явно. В списке неявно типизированных параметров типы параметров выводятся из контекста, в котором находится анонимная функция, в частности, когда анонимная функция преобразуется в совместимый тип делегата или дерева выражения, типы параметров предоставляются этим типом (§6.5).

В анонимной функции с одним параметром с неявной типизацией в списке параметров можно опустить скобки. Другими словами запись анонимной функции вида

( параметр ) => выражение

можно сократить до:

параметр => выражение

Список параметров анонимной функции в виде выражения_анонимного_метода является необязательным. Если они заданы, то параметры должны быть явно типизированы Если они не задаются, то анонимную функцию можно преобразовать в делегат с любым списком параметров, не содержащих параметров out.

Ниже приведены некоторые примеры анонимных функций.

x => x + 1 // Неявная типизация, тело выражения

x => { return x + 1; } // Неявная типизация, тело оператора

(int x) => x + 1 // Явная типизация, тело выражения

(int x) => { return x + 1; } // Явная типизация, тело оператора

(x, y) => x * y // Несколько параметров

() => Console.WriteLine() // Без параметров

delegate (int x) { return x + 1; } // Выражение анонимного метода

delegate { return 1 + 1; } // Список параметров опущен

Поведение лямбда_выражений и выражений_анонимных_методов совпадает за исключением следующих моментов:

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

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

· Тело лямбда_выражения может быть выражением или блоком оператора, тогда как тело выражения_анонимного_метода должно быть блоком оператора.

· Поскольку тело_выражения могут иметь только лямбда_выражения, выражения_анонимных_методов нельзя преобразовать в тип дерева выражения (§4.6).

7.14.1 Подписи анонимных функций

Необязательная подпись_анонимной_функции задает названия и (необязательно) имена формальных параметров анонимной функции. Область действия параметров анонимной функции — это тело_анонимной_функции. (§3.7) Вместе со списком параметров (если он предоставлен) тело анонимного метода задает пространства объявления (§3.3). Таким образом, если имя параметра анонимной функции совпадает с именем локальной переменной, локальной константы или параметра, область действия которого включает выражение_анонимного_метода или лямбда_выражение, то будет возникать ошибка времени компиляции.

Если у анонимной функции есть явная_подпись_анонимной_функции, то набор совместимых типов делегатов и типов дерева выражения ограничивается теми, у которых имеются одинаковые параметры типа и модификаторы в том же порядке. В отличие от преобразования группы методов (§6.6) контравариантность типов параметров анонимных функций не поддерживается. Если у анонимной функции нет подписи_анонимной_функции, то набор совместимых типов делегатов и типов деревьев выражений ограничивается тем, у которых нет параметров out.

Обратите внимание, что подпись_анонимной_функции не может включать атрибуты или массив параметров. Тем не менее подпись_анонимной_функции может быть совместима с типом делегата, список параметров которого содержит массив параметров.

Обратите внимание, что преобразование в тип дерева выражения, даже если он совместим, может привести к сбою во время компиляции (§4.6).

7.14.2 Тела анонимных функций

Тело (выражение или блок) анонимной функции должно удовлетворять следующим правилам.

· Если анонимная функция включает подпись, то параметры, указанные в подписи, должны быть доступны в теле. Если у анонимной функции нет подписи, она может быть преобразована в тип делегата или тип выражения с параметрами (§6.5), но обращаться к параметрам в теле будет нельзя.

· Если в теле происходит обращение к параметру ref или out, то возникает ошибка времени компиляции, за исключением случаев, когда параметры ref или out указываются в подписи (если она есть) ближайшей включающей анонимной функции.

· Если типом this является тип структуры, то при обращении в теле к this возникает ошибка времени выполнения. Это так при явном доступе (как в случае this.x) и при неявном (ср. x, где x является членом экземпляра структуры). Это правило просто запрещает такой доступ и не влияет на то, будет ли при поиске члена найден член структуры.

· В теле можно обращаться к внешним переменным (§7.14.4) анонимной функции. При доступе к внешней переменной будет сформирована ссылка на экземпляр переменной, активной во время вычисления лямбда_выражения или выражения_анонимного_метода (§7.14.5).

· При наличии в теле оператора goto, break или continue, назначением которого является расположение вне тела или внутри тела содержащейся анонимной функции, будет возникать ошибка времени компиляции.

· Оператор return в теле возвращает управление из вызова ближайшей включающей анонимной функции, а не из включающей функции-члена. Выражение, указанное в операторе return, должно быть совместимо с типом делегата или типом дерева выражения, в который преобразуется ближайшее включающее лямбда_выражение или выражение_анонимного_метода (§6.5).

Специально не указывается, существует ли способ выполнить блок анонимной функции, отличный от вычисления и вызова лямбда-выражения или выражения_анонимного_метода. В частности, компилятор может реализовать анонимную функцию, создав один или несколько именованных методов или типов. Имена любых таких созданных элементов должны иметь формат, зарезервированный для использования компилятором.

7.14.3 Разрешение перегрузки

Анонимные функции в списке аргументов участвуют в выводе типа и разрешении перегрузки. Подробные правила см. в разделе §7.4.2.3.

В следующем примере демонстрируется результат использования анонимных функций при разрешении перегрузки

class ItemList<T>: List<T>
{
public int Sum(Func<T,int> selector) {
int sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}

public double Sum(Func<T,double> selector) {
double sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
}

У класса ItemList<T> есть два метода Sum. У каждого метода есть аргумент selector, который извлекает значение для суммирования из списка элементов. Извлеченное значение может иметь тип int или double, и сумма в результате также может иметь тип int или double.

Методы Sum можно, например, использовать для вычисления сумм из списка строк с деталями заказов.

class Detail
{
public int UnitCount;
public double UnitPrice;
...
}

void ComputeSums() {
ItemList<Detail> orderDetails = GetOrderDetails(...);
int totalUnits = orderDetails.Sum(d => d.UnitCount);
double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
...
}

В первом вызове orderDetails.Sum оба метода Sum применимы, поскольку анонимная функция d => d.UnitCount совместима и с Func<Detail,int> и с Func<Detail,double>. Однако при разрешении перегрузки будет выбран первый метод Sum, потому что преобразование в Func<Detail,int> оказывается лучше преобразования в Func<Detail,double>.

Во втором вызове orderDetails.Sum применим только второй метод Sum, потому что анонимная функция d => d.UnitPrice * d.UnitCount возвращает значение типа double. Поэтому в данном вызове при разрешении перегрузки будет выбран второй метод Sum.

7.14.4 Внешние переменные

Любая локальная переменная, параметр значения или массив параметров, область действия которого включает лямбда_выражение или выражение_анонимного_метода, называется внешней переменной анонимной функции. В функции-члене экземпляра для класса значение this считается параметром значения и является внешней переменно любой анонимной функции, содержащейся в функции-члене.

7.14.4.1 Захваченные внешние переменные

Когда анонимная функция ссылается на внешнюю переменную, внешнюю переменную называют захваченной анонимной функцией. Обычно срок жизни локальной переменной ограничен выполнением блока или оператора, с которым она связана (§5.1.7). Однако срок жизни захваченной внешней переменной увеличивается по крайней мере до того, как делегат или дерево выражения, созданное из анонимной функции, становится объектом для процесса сборки мусора.

В данном примере

using System;

delegate int D();

class Test
{
static D F() {
int x = 0;
D result = () => ++x;
return result;
}

static void Main() {
D d = F();
Console.WriteLine(d());
Console.WriteLine(d());
Console.WriteLine(d());
}
}

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

1
2
3

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

7.14.4.2 Создание экземпляров локальных переменных

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

static void F() {
for (int i = 0; i < 3; i++) {
int x = i * 2 + 1;
...
}
}

Однако если перенести объявления x за пределы цикла, то экземпляр x будет создаваться только один раз.

static void F() {
int x;
for (int i = 0; i < 3; i++) {
x = i * 2 + 1;
...
}
}

Если локальная переменная не захвачена, то не существует способа точно отследить, сколько раз создавались ее экземпляры, потому что сроки жизни экземпляров не связаны. Для каждого экземпляра можно просто использовать одно место хранения. Однако когда локальная переменная захватывается анонимной функцией, результат создания экземпляра становится очевидным.

В примере

using System;

delegate void D();

class Test
{
static D[] F() {
D[] result = new D[3];
for (int i = 0; i < 3; i++) {
int x = i * 2 + 1;
result[i] = () => { Console.WriteLine(x); };
}
return result;
}

static void Main() {
foreach (D d in F()) d();
}
}

получается вывод

1
3
5

Однако при переносе объявления x за пределы цикла:

static D[] F() {
D[] result = new D[3];
int x;
for (int i = 0; i < 3; i++) {
x = i * 2 + 1;
result[i] = () => { Console.WriteLine(x); };
}
return result;
}

вывод имеет вид

5
5
5

Если в цикле for объявляется переменная итерации, то сама эта переменная считается объявленной за пределами цикла. Таким образом, если изменить пример, чтобы захватывать саму переменную итерации

static D[] F() {
D[] result = new D[3];
for (int i = 0; i < 3; i++) {
result[i] = () => { Console.WriteLine(i); };
}
return result;
}

то будет захвачен только один экземпляр переменной итерации. В этом случае вывод имеет вид

3
3
3

Делегаты анонимных функций могут иметь некоторые общие захваченные переменные и в то же время иметь отдельные экземпляры других переменных. Например, если F изменить на

static D[] F() {
D[] result = new D[3];
int x = 0;
for (int i = 0; i < 3; i++) {
int y = 0;
result[i] = () => { Console.WriteLine("{0} {1}", ++x, ++y); };
}
return result;
}

то три делегата будут захватывать один экземпляр x, но разные экземпляры y. Вывод будет иметь вид:

1 1
2 1
3 1

Отдельные анонимные функции могут захватывать один экземпляр внешней переменной. В данном примере:

using System;

delegate void Setter(int value);

delegate int Getter();

class Test
{
static void Main() {
int x = 0;
Setter s = (int value) => { x = value; };
Getter g = () => { return x; };
s(5);
Console.WriteLine(g());
s(10);
Console.WriteLine(g());
}
}

две анонимные функции захватывают один экземпляр локальной переменной x и поэтому они могут «взаимодействовать» через эту переменную. Вывод в данном примере имеет вид:

5
10

7.14.5 Вычисление выражений анонимных функций

Анонимная функция F всегда должна преобразовываться в тип делегата D или тип дерева выражения E либо напрямую, либо с помощью выполнения выражения создания делегата new D(F). Это преобразование определяет результат анонимной функции, как описано в разделе §6.5.

7.15 Выражения запросов

Выражения запросов представляют собой подмножество синтаксических конструкций языка для запросов, похожих запросы иерархических или реляционных языков запросов, таких как SQL и XQuery.

выражение запроса:
предложение from тело запроса

предложение from:
from типнеобяз идентификатор in выражение

тело запроса:
предложения тела запросанеобяз предложение select или group продолжение запросанеобяз

предложения тела запроса:
предложение тела запроса
предложения тела запроса предложение тела запроса

предложение тела запроса:
предложение from
предложение let
предложение where
предложение join
предложение join into
предложение orderby

предложение let:
let идентификатор = выражение

предложение where:
where логическое выражение

предложение join:
join типнеобяз идентификатор in выражение on выражение equals выражение

предложение join into:
join типнеобяз идентификатор in выражение on выражение equals выражение into идентификатор

предложение orderby:
orderby упорядочения

упорядочения:
упорядочение
упорядочения , упорядочение

упорядочение:
выражение направление упорядочениянеобяз

направление упорядочения:
ascending
descending

предложение select или group:
предложение select
предложение group

предложение select:
select выражение

предложение group:
group выражение by выражение

продолжение запроса:
into идентификатор тело запроса

Выражение запроса начинается с предложения from и заканчивается предложением select или group. После исходного предложения from могут идти предложения from, let, where, join или orderby. Каждое предложение from является генератором, предлагающим переменную диапазона, которая включает элементы последовательности. Каждое предложение let вводит переменную диапазона, которая представляет значение, вычисляемое с помощью предыдущих переменных диапазона. Каждое предложение where является фильтром для исключения элементов из результата. Каждое предложение join сравнивает указанные ключи исходной последовательности с ключами другой последовательности, выдавая совпадающие пары. Каждое предложение orderby изменят порядок элементов в соответствии с указанными критериями. Конечное предложение select или group задает формат результата в виде переменных диапазона. И наконец, предложение into можно использовать для «склеивания» запросов, рассматривая результаты одного запроса в качестве генератора для последующего запроса.

7.15.1 Неоднозначность в выражениях запросов

Выражения запросов содержат определенное число «контекстных ключевых слов», то есть идентификаторов, у которых есть особое значение в конкретном контексте. В частности, это from, where, join, on, equals, into, let, orderby, ascending, descending, select, group и by. Чтобы не допустить появления неоднозначности в выражениях запросов, вызванной смешанным использованием этих идентификаторов в виде ключевых слов и простых имен, при появлении в любом месте выражения запроса такие идентификаторы считаются ключевыми словами.

В этом смысле выражением запроса является любое выражение, которое начинается со строки «from идентификатор», за которым следует любая лексема кроме «, «=» или «.

Чтобы использовать эти слова в качестве идентификаторов в выражении запроса, перед ними можно указать «@» (§2.4.2).

7.15.2 Перевод выражений запросов

В языке C# не задается семантика выполнения выражений запросов. Вместо этого выражения запросов переводятся в вызовы методов, которые соответствуют шаблону выражения запроса (§7.15.3). В частности, выражения запросов переводятся в вызовы методов с именами Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy и Cast. Эти методы должны иметь специальные подписи и типы результатов, как описано в разделе §7.15.3. Эти методы могут быть методами экземпляра запрашиваемого объекта или методами расширения, внешними для объекта, и в них может реализовываться фактическое выполнение запроса.

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

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

При некоторых переводах вставляются переменные диапазона с прозрачными идентификаторами, обозначенными *. Особые свойства прозрачных идентификаторов рассматриваются в разделе §7.15.2.7.

7.15.2.1 Предложения select и groupby с продолжениями

Выражение запроса с продолжением

from … into x …

переводится в

from x in ( from … ) …

В переводах в следующих разделах предполагается, что в запросах нет продолжений into.

Пример

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

переводится в

from g in
from c in customers
group c by c.Country
select new { Country = g.Key, CustCount = g.Count() }

конечный перевод имеет вид

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

7.15.2.2 Явные типы переменных диапазона

Предложение from, явно указывающее тип переменной диапазона

from T x in e

переводится в

from x in ( e ) . Cast < T > ( )

Предложение join, явно указывающее тип переменной диапазона

join T x in e on k1 equals k2

переводится в

join x in ( e ) . Cast < T > ( ) on k1 equals k2

В переводах в следующих разделах предполагается, что в запросах нет явных типов переменных диапазона.

Пример

from Customer c in customers
where c.City == "London"
select c

переводится в

from c in customers.Cast<Customer>()
where c.City == "London"
select c

конечный перевод имеет вид

customers.
Cast<Customer>().
Where(c => c.City == "London")

Явные типы переменных диапазона удобно использовать при запросе к коллекциям, в которых реализуется неуниверсальный интерфейс IEnumerable, но не универсальный интерфейс IEnumerable<T>. В примере выше это имело бы место, если бы таблица customers имела тип ArrayList.

7.15.2.3 Выражения вырожденных запросов

Выражение запроса вида

from x in e select x

переводится в

( e ) . Select ( x => x )

Пример

from c in customers
select c

переводится в

customers.Select(c => c)

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

From, let, where, join и orderby

from x1 in e1 from x2 in e2 select v переводится… ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )

Select

Выражение запроса вида

from x in e select v

переводится в

( e ) . Select ( x => v )

за исключением случая, когда v является идентификатором x, тогда перевод имеет вид просто

( e )

Например:

from c in customers.Where(c => c.City == “London”)
select c

переводится просто в

customers.Where(c => c.City == “London”)

Groupby

from x in e group v by k переводится… ( e ) . GroupBy ( x => k , x => v )

7.15.2.7 Прозрачные идентификаторы

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

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

· Когда прозрачный идентификатор является параметром анонимной функции, члены связанного анонимного типа автоматически оказываются в области действия в теле анонимной функции.

· Когда член с прозрачным идентификатором находится в области действия, члены этого члена также находятся в области действия.

· Когда прозрачный идентификатор оказывается в роли декларатора члена в инициализаторе анонимного объекта, он создает член с прозрачным идентификатором.

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

Пример

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

переводится в

from * in customers.
SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

и далее переводится в

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(* => o.Total).
Select(* => new { c.Name, o.Total })

что после удаления прозрачных идентификаторов эквивалентно

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.Total })

где x — идентификатор, созданный компилятором, который в других условиях является невидимым и недоступным.

Пример

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

переводится в

from * in customers.
Join(orders, c => c.CustomerID, o => o.CustomerID,
(c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

что дальше сокращается до

customers.
Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }).
Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d }).
Join(products, * => d.ProductID, p => p.ProductID, (*, p) => new { *, p }).
Select(* => new { c.Name, o.OrderDate, p.ProductName })

конечный перевод имеет вид

customers.
Join(orders, c => c.CustomerID, o => o.CustomerID,
(c, o) => new { c, o }).
Join(details, x => x.o.OrderID, d => d.OrderID,
(x, d) => new { x, d }).
Join(products, y => y.d.ProductID, p => p.ProductID,
(y, p) => new { y, p }).
Select(z => new { z.y.x.c.Name, z.y.x.o.OrderDate, z.p.ProductName })

где x, y и z — идентификаторы, созданные компилятором, которые в других условиях являются невидимыми и недоступными.

7.15.3 Шаблон выражения запроса

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

Ниже представлен рекомендуемый формат универсального типа C<T>, который поддерживает шаблон выражения запроса. Универсальный тип используется, чтобы продемонстрировать правильные отношения между параметрами и результирующими типами, но шаблон также можно реализовать и для неуниверсальных типов.

delegate R Func<T1,R>(T1 arg1);

delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
public C<T> Cast<T>();
}

class C<T> : C
{
public C<T> Where(Func<T,bool> predicate);

public C<U> Select<U>(Func<T,U> selector);

public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector);

public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);

public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);

public O<T> OrderBy<K>(Func<T,K> keySelector);

public O<T> OrderByDescending<K>(Func<T,K> keySelector);

public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);

public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector);
}

class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector);

public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}

class G<K,T> : C<T>
{
public K Key { get; }
}

В методах выше используются универсальные типы делегатов Func<T1, R> и Func<T1, T2, R>, но в них так же успешно можно было бы использовать другие типы делегатов или деревьев выражений с такими же отношениями между типами параметров и результатов.

Обратите внимание на рекомендуемое отношение между C<T> и O<T>, которое гарантирует, что методы ThenBy и ThenByDescending будут доступны только для результата операторов OrderBy или OrderByDescending. Также обратите внимание на рекомендуемый формат результата оператора GroupBy — последовательность последовательностей, где каждая внутренняя последовательность имеет дополнительное свойство Key.

Пространство имен System.Linq предоставляет реализацию шаблона операторов запроса для любого типа, в котором реализуется интерфейс System.Collections.Generic.IEnumerable<T>.

7.16 Операторы присваивания

Операторы присваивания назначают новое значение переменной, свойству, событию или элементу индексатора.

присваивание:
унарное выражение оператор присваивания выражение

оператор присваивания:
=
+=
-=
*=
/=
%=
&=
|=
^=
<<=
присваивание со сдвигом вправо

Левый операнд присваивания должен быть выражением с классом переменной, доступа к свойству, доступа к индексатору или доступа к событию.

Оператор = называется простым оператором присваивания. Он присваивает значение правого операнда переменной, свойству или элементу индексатора, который представлен левым операндом. Левый операнд оператора простого присваивания не может быть доступом к событию (за исключением случая, описанного в разделе §10.8.1). Простой оператор присваивания описывается в разделе §7.16.1.

Операторы присваивания, отличные от оператора =, называются сложными операторами присваивания. Эти операторы выполняют указанную операцию для двух операндов, а затем присваивают результирующее значение переменной, свойству или элементу индексатора, представленному левым операндом. Сложные операторы присваивания описываются в разделе §7.16.2.

Операторы += и -= с выражением доступа к событию в качестве левого операнда называются операторами_присваивания_события. С доступом к событию в качестве левого операнда не допустим ни один другой оператор присваивания. Операторы присваивания события описываются в разделе §7.16.3.

Операторы присваивания имеют правую ассоциативность; это означает, что операции группируются справа налево. Например, выражение вида a = b = c вычисляется как a = (b = c).

7.16.1 Простое присваивание

Оператор = называется простым оператором присваивания. При простом присваивании правый операнд должен быть выражением с типом, который может быть неявно преобразован в тип левого операнда. Операция присваивает значение правого операнда переменной, свойству или элементу индексатора, который представлен левым операндом.

Результатом выражения простого присваивания является присваивание значения левому операнду. Результат имеет такой же тип, что и левый операнд, и всегда классифицируется как значение.

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

Во время выполнения обработка простого присваивания вида x = y включает следующие этапы.

· Если x классифицируется как переменная, то:

o x вычисляется для создания переменной.

o Вычисляется y и при необходимости преобразуется в тип x с помощью неявного преобразования (§6.1).

o Если переменная, представленная x, является элементом массива ссылочного_типа, то во время выполнения проводится проверка с целью убедиться, что значение, вычисленное для y совместимо с экземпляром массива, элементом которого является x. Проверка оказывается успешной, если y равно null или если существует неявное преобразование значения ссылочного типа (§6.1.6) из фактического типа экземпляра, на который ссылается y, в фактический тип элемента экземпляра массива, содержащего x. В противном случае возникает исключение System.ArrayTypeMismatchException.

o Значение, получающееся после вычисления и преобразования y, сохраняется в расположении, которое задается значением x.

· Если x классифицируется как свойство или доступ к индексатору, то:

o Вычисляются выражение экземпляра (если x не имеет тип static) и список аргументов (если x является доступом к экземпляру), связанные с x, и полученные результаты используются при последующем вызове метода доступа set.

o Вычисляется y и при необходимости преобразуется в тип x с помощью неявного преобразования (§6.1).

o Вызывается метод доступа set для x со значением, вычисленным для y, в качестве своего аргумента value.

По правилам ковариации массива (§12.5) значение массива типа A[] может быть ссылкой на экземпляр массива типа B[], если существует неявное преобразование ссылочного типа из B в A. В соответствии с этими правилами присваивание элементу массива ссылочного_типа требует проведения во время выполнения проверки с целью убедиться, что присваиваемое значение совместимо с экземпляром массива. В данном примере

string[] sa = new string[10];
object[] oa = sa;

oa[0] = null; // Ok
oa[1] = "Hello"; // Ok
oa[2] = new ArrayList(); // ArrayTypeMismatchException

последнее присваивание вызывает исключение System.ArrayTypeMismatchException, потому что экземпляр ArrayList нельзя сохранить в элементе типа string[].

Когда назначением присваивания является свойство или индексатор типа_структуры, выражение экземпляра, связанное со свойством или доступом к индексатору, должно классифицироваться как переменная. Если выражение экземпляра классифицируется как значение, возникает ошибка времени компиляции. В силу §7.5.4 такое же правило применяется к полям.

При наличии объявлений:

struct Point
{
int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public int X {
get { return x; }
set { x = value; }
}

public int Y {
get { return y; }
set { y = value; }
}
}

struct Rectangle
{
Point a, b;

public Rectangle(Point a, Point b) {
this.a = a;
this.b = b;
}

public Point A {
get { return a; }
set { a = value; }
}

public Point B {
get { return b; }
set { b = value; }
}
}

в примере

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

присваивания для p.X, p.Y, r.A и r.B будут допустимы, потому что p и r являются переменными. Однако в примере

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

присваивания будут недопустимы, поскольку r.A и r.B не являются переменными.

7.16.2 Сложное присваивание

Операция вида x && y или x || y обрабатывается с применением разрешения перегрузки (§7.2.4), как если бы операция записывалась в виде x op y. Тогда

· Если тип возвращаемого значения выбранного оператора может быть неявно преобразован в тип x, то операция вычисляется как x = x op y, за исключением того, что x вычисляется только один раз.

· Если выбранный оператор является стандартным оператором, то если тип возвращаемого значения выбранного оператора может быть явно преобразован в тип x и если y может быть неявно преобразован в тип x или оператор является оператором сдвига, то операция вычисляется как x = (T)(x op y), где T имеет тип x, за исключением того, что x вычисляется только один раз.

· В противном случае сложное присваивание является недопустимым и возникает ошибка времени компиляции.

Выражение «вычисляется только один раз» означает, что при вычислении x op y, результаты любого составляющего выражения в x временно сохраняются и затем используются повторно при присваивании для x. Например, в присваивании A()[B()] += C(), где A является методом, возвращающим значение int[], а B и C являются методами, возвращающими значение int, эти методы вызываются только один раз в последовательности A, B, C.

Когда левый операнд сложного присваивания является доступом к свойству или доступом к индексатору, свойство или индексатор должны иметь и метод доступа get, и метод доступа set. В противном случае возникает ошибка времени компиляции.

Второе правило (см. выше) позволяет в определенных контекстах вычислять x op= y как x = (T)(x op y). Существует правило, согласно которому стандартные операторы можно использовать в качестве сложных операторов, когда левый операнд имеет тип sbyte, byte, short, ushort или char. Даже если оба аргумента имеют один из этих типов, стандартные операторы дают результат типа int, как описано в разделе §7.2.6.2. Таким образом, без приведения типов присвоить результат левому операнду не удастся.

Интуитивным результатом применения правила для стандартных операторов является просто то, что операция x op= y допустима, если допустимы обе операции x op y и x = y. В данном примере

byte b = 0;
char ch = '\0';
int i = 0;

b += 1; // Ok
b += 1000; // Ошибка, b = 1000 не допустимо
b += i; // Ошибка, b = i не допустимо
b += (byte)i; // Ok

ch += 1; // Ошибка, ch = 1 не допустимо
ch += (char)1; // Ok

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

Это также означает, что сложные операции присваивания поддерживают операции с нулификацией. В данном примере

int? i = 0;
i += 1; // Ok

используется оператор с нулификацией +(int?,int?).

7.16.3 Присваивание событий

Если левый операнд оператора += или -= классифицируется как доступ к свойству, то выражение вычисляется следующим образом.

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

· Вычисляется правый операнд оператор += или -= и при необходимости преобразуется в тип левого операнда с помощью неявного преобразования (§6.1).

· Вызывается доступ к событию со списком аргументов, состоящим из правого операнда после вычисления и при необходимости после преобразования. Если оператор равен +=, вызывается метод доступа add; если оператор равен -=, вызывается метод доступа remove.

Выражение присваивания события не порождает значения. Таким образом, выражение присваивания события допустимо только в контексте выражения_оператора (§8.6).

7.17 Выражение

Выражение является либо выражением_не_присваивания, либо присваиванием.

выражение:
выражение не присваивания
присваивание

выражение не присваивания:
условное выражение
лямбда-выражение
выражение запроса

7.18 Константные выражения

Константное_выражение — это выражение, которое можно полностью вычислить во время компиляции.

константное выражение:
выражение

Константное выражение должно быть литералом null или значением одного из следующих типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string или любым типом перечисления. В константных выражениях допустимы только следующие конструкции:

· Литералы (включая литерал null).

· Ссылки на константные члены типов класса и структуры.

· Ссылки на члены типов перечисления.

· Ссылки на константные параметры или локальные переменные

· Вложенные выражения в скобках, которые сами являются константными выражениями.

· Выражения приведения типа при условии, что целевой тип входит в список типов, указанных выше.

· Выражения checked и unchecked

· Выражения значения по умолчанию

· Стандартные унарные операторы +, –, ! и ~.

· Стандартные бинарные операторы +, –, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <= и >= при условии, что каждый операнд имеет тип, указанный в списке выше.

· Условный оператор?:.

В константных выражения допустимы следующие преобразования:

· Преобразования идентификатора

· Числовые преобразования

· Преобразования перечисления

· Преобразования константных выражений

· Явные и неявные преобразования значений ссылочного типа при условии, что источник преобразования является константным выражением, которое дает значение null.

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

class C {
const object i = 5; // ошибка: преобразование упаковки недопустимо
const object str = “hello”; // ошибка: неявное преобразование ссылочного типа
}

здесь инициализация i приводит к ошибке, потому что требуется преобразование упаковки. Инициализация str вызывает ошибку, потому что требуется неявное преобразование ссылочного типа из ненулевого значения.

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

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

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

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

· Объявления констант (§10.4).

· Объявления членов перечислений (§14.3).

· Метки case в операторе switch (§8.7.2).

· Операторы goto case (§8.9.3).

· Длины измерений в выражениях создания массивов (§7.5.10.4), которые включают инициализатор.

· Атрибуты (§17).

Неявное преобразование константного выражения (§6.1.8) допускает преобразование константного выражения с типом int в тип sbyte, byte, short, ushort, uint или ulong при условии, что значение константного выражения находится в пределах диапазона целевого типа.

7.19 Логические выражения

Логическое_выражение — это выражение, которое дает результат типа bool.

логическое_выражение:
выражение

Логическим_выражением является управляющее условное выражение оператора_if (§8.7.1), оператора_while (§8.8.1), оператора_do (§8.8.2) оператора_for (§8.8.3). Для управляющего выражения оператора?: (§7.13) действуют те же правила, что и для логического_выражения, но из-за приоритетов операторов оно классифицируется как условное_выражение_ИЛИ.

Логическое_выражение должно иметь тип, который может быть неявно преобразован в тип bool или тип, в котором реализуется оператор operator true. Если не выполняется ни одно из этих требований, то возникает ошибка времени компиляции.

Если логическое выражение имеет тип, который нельзя неявно преобразовать в тип bool, но в этом типе реализуется оператор operator true, то для получения логического значения после вычисления выражения вызывается реализация оператора operator true из этого типа.

Тип структуры DBBool в разделе §11.4.2 является примером типа, в котором реализуется оператор operator true и operator false.

8. Операторы языка

Язык C# содержит множество операторов. Большинство из них будут знакомы разработчикам, имеющим опыт программирования на языках C и C++.

оператор_языка:
помеченный_оператор
оператор_объявления
внедренный_оператор

внедренный_оператор:
блок
пустой_оператор
оператор_выражение
оператор_выбора
оператор_итераций
оператор_перехода
оператор_try
оператор_checked
оператор_unchecked
оператор_lock
оператор_using
оператор_yield

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

void F(bool b) {
if (b)
int i = 44;
}

результатом будет ошибка времени компиляции, поскольку для оператора if требуется использовать внедренный_оператор в ветви if, а не оператор_языка. Если бы такой код был разрешен, переменная i стала бы объявленной, но ее нельзя было бы использовать. Следует, однако, отметить, что помещение объявления i в блок делает пример допустимым.

8.1 Конечные точки и достижимость

У каждого оператора языка имеется конечная точка. Интуитивно говоря, конечная точка оператора — это позиция, непосредственно следующая за оператором. Правилами выполнения составных операторов (операторов языка, содержащих внедренные операторы) определяется действие, которое предпринимается, когда управление достигает конечной точки внедренного оператора. Например, когда управление достигает конечной точки оператора в блоке, оно передается следующему оператору этого блока.

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

В следующем примере

void F() {
Console.WriteLine("reachable");
goto Label;
Console.WriteLine("unreachable");
Label:
Console.WriteLine("reachable");
}

второй вызов функции Console.WriteLine недостижим, потому что он не может быть выполнен ни при каких условиях.

Если компилятором установлен факт недостижимости оператора языка, выдается предупреждение. Недостижимость оператора не рассматривается как ошибка.

Чтобы определить, достижим ли данный оператор языка или конечная точка, компилятор выполняет анализ потока управления в соответствии с правилами достижимости, установленными для каждого оператора. В ходе анализа принимаются во внимание значения константных выражений (§7.18), контролирующих поведение операторов, но возможные значения неконстантных выражений не учитываются. Иными словами, при анализе потока управления считается, что неконстантное выражение данного типа может принимать любое значение этого типа.

В следующем примере

void F() {
const int i = 1;
if (i == 2) Console.WriteLine("unreachable");
}

логическое выражение оператора if является константным выражением, поскольку оба операнда оператора == представляют собой константы. Поскольку константное выражение вычисляется во время компиляции и его значением оказывается false, вызов Console.WriteLine считается недостижимым. Однако если переменную i сделать локальной:

void F() {
int i = 1;
if (i == 2) Console.WriteLine("reachable");
}

то вызов Console.WriteLine станет достижимым, хотя на самом деле и не будет никогда выполняться.

Блок функции-члена всегда считается достижимым. Последовательно применяя правила достижимости каждого оператора блока, можно определить достижимость любого конкретного оператора языка.

В следующем примере

void F(int x) {
Console.WriteLine("start");
if (x < 0) Console.WriteLine("negative");
}

достижимость второго вызова Console.WriteLine устанавливается следующим образом:

· Первый оператор-выражение Console.WriteLine достижим, поскольку блок метода F является достижимым.

· Конечная точка первого оператора-выражения Console.WriteLine достижима, поскольку сам оператор достижим.

· Оператор if достижим, поскольку конечная точка первого оператора-выражения Console.WriteLine достижима.

· Второй оператор-выражение Console.WriteLine достижим, поскольку значением логического выражения оператора if не является константа false.

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

· Поскольку оператор switch не позволяет перейти из одного раздела switch в следующий, достижимость конечной точки списка операторов раздела switch распознается как ошибка компиляции. Обычно такая ошибка свидетельствует об отсутствии оператора break.

· Достижимость конечной точки блока функции-члена, вычисляющего значение, распознается как ошибка компиляции. Обычно такая ошибка свидетельствует об отсутствии оператора return.

8.2 Блоки

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

блок:
{ список_операторовнеоб }

Блок состоит из необязательного списка_операторов (§8.2.1), заключенного в фигурные скобки. Если список операторов опущен, говорят, что блок пустой.

Блок может включать операторы объявления (§8.5). Областью видимости локальной переменной или константы, объявленной в блоке, является этот блок.

Значение имени, используемого в контексте выражения, всегда должно быть одинаковым в пределах блока (§7.5.2.1).

Блок выполняется следующим образом.

· Если блок пустой, управление передается в конечную точку блока.

· Если блок непустой, управление передается в список операторов. Если управление достигает конечной точки списка операторов, после этого управление передается в конечную точку блока.

Список операторов блока считается достижимым, если сам блок является достижимым.

Конечная точка блока достижима, если блок пустой или если конечная точка списка операторов достижима.

Блок, содержащий один или несколько операторов yield (§8.14), называется блоком итератора. Блоки итераторов используются для реализации функций-членов в виде итераторов (§10.14). В отношении блоков итераторов действуют дополнительные ограничения.

· Появление оператора return в блоке итератора распознается как ошибка времени компиляции (но при этом операторы yield return разрешены).

· Наличие небезопасного контекста в блоке итератора распознается как ошибка времени компиляции (§18.1). Блок итератора всегда определяет безопасный контекст, даже если его объявление вложено в небезопасный контекст.

8.2.1 Списки операторов

Список операторов состоит из одного или нескольких последовательно записанных операторов языка. Списки операторов могут входить в блоки (§8.2) и в блоки_switch (§8.7.2).

список_операторов:
оператор_языка
список_операторов оператор_языка

Выполнение списка операторов начинается с передачи управления первому оператору. Если управление достигает конечной точки оператора, после этого управление передается следующему оператору. Если управление достигает конечной точки последнего оператора, после этого управление передается в конечную точку списка операторов.

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

· Оператор является первым в списке и сам список операторов является достижимым.

· Конечная точка предыдущего оператора достижима.

· Оператор является помеченным и его метка указывается в достижимом операторе goto.

Конечная точка списка операторов достижима, если достижима конечная точка последнего оператора языка в списке.

8.3 Пустой оператор

Пустой_оператор не выполняет никаких действий.

пустой_оператор:
;

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

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

Пустой оператор может использоваться при записи оператора while с пустым телом:

bool ProcessMessage() {...}

void ProcessMessages() {
while (ProcessMessage())
;
}

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

void F() {
...

if (done) goto exit;
...

exit: ;
}

8.4 Помеченные операторы

Помеченный_оператор позволяет предварить оператор языка меткой. Помеченные операторы разрешается использовать в блоках, но запрещается использовать как внедренные операторы.

помеченный_оператор:
идентификатор : оператор_языка

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

Метка может указываться в операторах goto (§8.9.3), находящихся в области ее видимости. Это означает, что оператор goto может передавать управление в пределах блока и за его пределы, но не внутрь блока.

Метки имеют собственную область объявления и не вступают в конфликт с другими идентификаторами. Пример

int F(int x) {
if (x >= 0) goto x;
x = -x;
x: return x;
}

является допустимым; в нем имя x используется и как параметр, и как метка.

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

Помимо достижимости в рамках обычного потока управления, помеченный оператор может быть достижимым в случае, если его метка указывается в достижимом операторе goto. (Имеется исключение: если оператор goto находится внутри оператора try, включающего блок finally, а помеченный оператор находится вне оператора try, и при этом конечная точка блока finally недостижима, то помеченный оператор недостижим из такого оператора goto.)

8.5 Операторы объявления

Оператор_объявления объявляет локальную переменную или константу. Операторы объявления разрешается использовать в блоках, но запрещается использовать как внедренные операторы.

оператор_объявления:
объявление_локальной_переменной ;
объявление_локальной_константы ;

8.5.1 Объявления локальных переменных

Объявление_локальной_переменной объявляет одну или несколько локальных переменных.

объявление_локальной_преременной:
тип_локальной_переменной деклараторы_локальных_переменных

тип_локальной_переменной:
тип
var

деклараторы_локальных_переменных:
декларатор_локальной_переменной
деклараторы_локальных_переменных , декларатор_локальной_переменной

декларатор_локальной_переменной:
идентификатор
идентификатор = инициализатор_локальной_переменной

инициализатор_локальной_переменной:
выражение
инициализатор_массива

Тип_локальной_переменной в объявлении_локальной_переменной либо непосредственно задает тип переменных, представленных в данном объявлении, либо с помощью ключевого слова var показывает, что тип неявно определяется инициализатором. За типом следует список деклараторов_локальных_переменных, каждый из которых представляет новую переменную. Декларатор_локальной_переменной состоит из идентификатора, определяющего имя переменной, за которым могут следовать лексема = и инициализатор_локальной_переменной, присваивающий переменной начальное значение.

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

· Объявление_локальной_переменной не может содержать несколько деклараторов_локальных_переменных.

· Декларатор_локальной_переменной должен включать инициализатор_локальной_переменной.

· Инициализатор_локальной_переменной должен представлять собой выражение.

· Выражение инициализатора должно иметь тип, определяемый во время компиляции.

· Выражение инициализатора не может ссылаться на саму объявляемую переменную.

Далее приводятся примеры неверных объявлений неявно введенных локальных переменных:

var x; // Ошибка, нет инициализатора для определения типа
var y = {1, 2, 3}; // Ошибка, недопустимый инициализатор массива
var z = null; // Ошибка, у значения null нет типа
var u = x => x + 1; // Ошибка, у анонимных функций нет типа
var v = v++; // Ошибка, инициализатор ссылается на переменную

Значение локальной переменной получается из выражения с использованием простого_имени (§7.5.2), а изменяется значение локальной переменной путем присваивания (§7.16). Локальная переменная требует определенного присваивания (§5.3) везде, где получается ее значение.

Областью видимости локальной переменной, описанной в объявлении_локальной_переменной, является блок, в котором встречается объявление. Ссылка на локальную переменную в позиции текста, предшествующей декларатору_локальной_переменной, рассматривается как ошибка. Объявление локальной переменной или константы в области видимости другой локальной переменной с тем же именем распознается как ошибка времени компиляции.

Объявление нескольких локальных переменных эквивалентно нескольким объявлениям одиночных переменных одного и того же типа. Кроме того, использование инициализатора в объявлении локальной переменной в точности эквивалентно вставке оператора присваивания непосредственно после объявления.

Пример

void F() {
int x = 1, y, z = x * 2;
}

в точности соответствует следующем коду:

void F() {
int x; x = 1;
int y;
int z; z = x * 2;
}

В объявлении неявно введенной локальной переменной в качестве типа объявляемой переменной принимается тип выражения, используемого для инициализации переменной. Например:

var i = 5;
var s = "Hello";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();

Эти объявления неявно введенных локальных переменных в точности эквивалентны следующим объявлениям с явным определением типа:

int i = 5;
string s = "Hello";
double d = 1.0;
int[] numbers = new int[] {1, 2, 3};
Dictionary<int,Order> orders = new Dictionary<int,Order>();

8.5.2 Объявления локальных констант

Объявление_локальной_константы объявляет одну или несколько локальных констант.

объявление_локальной_константы:
const тип деклараторы_констант

деклараторы_констант:
декларатор_константы
деклараторы_констант , деклараторы_констант

декларатор_константы:
идентификатор = константное_выражение

Тип в объявлении_локальной_константы задает тип констант, представленных в объявлении. За типом следует список деклараторов_констант, каждый из которых представляет новую константу. Декларатор_константы состоит из идентификатора, определяющего имя константы, за которым следуют лексема = и константное_выражение (§7.18), задающее значение константы.

Тип и константное_выражение в объявлении локальной константы подчиняются тем же правилам, что и в объявлении члена-константы (§10.4).

Значение локальной константы получается из выражения с использованием простого_имени (§7.5.2).

Областью видимости локальной константы является блок, в котором встречается объявление. Ссылка на локальную константу в позиции текста, предшествующей ее декларатору_константы, рассматривается как ошибка. Объявление локальной переменной или константы в области видимости другой локальной константы с тем же именем распознается как ошибка времени компиляции.

Объявление нескольких локальных констант эквивалентно нескольким объявлениям одиночных констант одного и того же типа.

8.6 Операторы-выражения

Оператор_выражение служит для вычисления данного выражения. Значение, вычисленное выражением (если оно получено), не сохраняется.

оператор_выражение:
выражение_оператора ;

выражение_оператора:
выражение_вызова
выражение_создания_объекта
присваивание
выражение_после_инкремента
выражение_после_декремента
выражение_перед_инкрементом
выражение_перед_декрементом

Не все выражения разрешается использовать как операторы языка. В частности, такие выражения, как x + y или x == 1, которые просто вычисляют значение (впоследствии отбрасываемое), недопустимы в качестве операторов языка.

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

8.7 Операторы выбора

Оператор выбора выбирает для выполнения один из нескольких возможных операторов языка на основании значения заданного выражения.

оператор_выбора:
оператор_if
оператор_switch

If

Оператор if выбирает оператор языка для выполнения на основании значения логического выражения.

оператор_if:
if ( логическое_выражение ) внедренный_оператор
if ( логическое_выражение ) внедренный_оператор else внедренный_оператор

Компонент else относится к лексикографически ближайшему предшествующему компоненту if, разрешенному правилами синтаксиса. Так, оператор if в виде

if (x) if (y) F(); else G();

эквивалентен следующему:

if (x) {
if (y) {
F();
}
else {
G();
}
}

Оператор if выполняется следующим образом.

· Вычисляется логическое_выражение (§7.19).

· Если результатом логического выражения является true, управление передается первому внедренному оператору. Если управление достигает конечной точки этого оператора, после этого управление передается в конечную точку оператора if.

· Если результатом логического выражения является false и в операторе имеется компонент else, управление передается второму внедренному оператору. Если управление достигает конечной точки этого оператора, после этого управление передается в конечную точку оператора if.

· Если результатом логического выражения является false и компонент else отсутствует, управление передается в конечную точку оператора if.

Первый внедренный оператор в составе оператора if считается достижимым, если оператор if достижим и значением логического выражения не является константа false.

Второй внедренный оператор оператора if (если он имеется) достижим, если оператор if достижим и значением логического выражения не является константа true.

Конечная точка оператора if достижима, если достижима конечная точка по крайней мере одного из его внедренных операторов. Кроме того, конечная точка оператора if без компонента else считается достижимой, если оператор if достижим и значением логического выражения не является константа true.

Switch

Оператор switch выбирает для выполнения список операторов, метка которого соответствует значению switch-выражения.

оператор_switch:
switch ( выражение ) блок_switch

блок_switch:
{ разделы_switchнеоб }

разделы_switch:
раздел_switch
разделы_switch раздел_switch

раздел_switch:
метки_switch список_операторов

метки_switch:
метка_switch
метки_switch метка_switch

метка_switch:
case константное_выражение :
default :

Оператор_switch состоит из ключевого слова switch, за которым следуют выражение в скобках (так называемое switch-выражение) и затем блок_switch. Блок_switch состоит из произвольного (возможно, нулевого) числа разделов_switch, заключенных в фигурные скобки. Каждый раздел_switch состоит из одной или нескольких меток_switch и следующего за ними списка_операторов (§8.2.1).

Определяющий тип оператора switch устанавливается switch-выражением. Если типом switch-выражения является sbyte, byte, short, ushort, int, uint, long, ulong, char, string или перечисляемый_тип, то он и считается определяющим типом оператора switch. В противном случае должно существовать ровно одно пользовательское неявное преобразование (§6.4) типа switch-выражения в один из следующих возможных определяющих типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, string. Если такого неявного преобразования не существует или определено несколько таких неявных преобразований, распознается ошибка времени компиляции.

Константное выражение каждого раздела case должно представлять значение, тип которого допускает неявное преобразование (§6.1) в определяющий тип оператора switch. Ошибка времени компиляции возникает, если несколько меток case в одном операторе switch задают одно и то же константное значение.

В операторе switch может быть не более одной метки default.

Оператор switch выполняется следующим образом.

· Вычисляется switch-выражение, которое преобразуется в определяющий тип.

· Если одна из констант, указанных в метке case того же оператора switch, совпадает со значением switch-выражения, управление передается списку операторов, следующему за такой меткой case.

· Если ни одна из констант, указанных в метках case того же оператора switch, не совпадает со значением switch-выражения и при этом имеется метка default, управление передается списку операторов, следующему за меткой default.

· Если ни одна из констант, указанных в метках case того же оператора switch, не совпадает со значением switch-выражения и метка default отсутствует, управление передается в конечную точку оператора switch.

Если конечная точка списка операторов в разделе switch достижима, распознается ошибка времени компиляции. Это так называемое правило «запрета последовательного выполнения». Следующий пример

switch (i) {
case 0:
CaseZero();
break;
case 1:
CaseOne();
break;
default:
CaseOthers();
break;
}

является допустимым, поскольку в нем нет разделов switch с достижимой конечной точкой. В отличие от языков C и C++, при выполнении раздела switch не допускается выход в следующий раздел switch, поэтому в примере

switch (i) {
case 0:
CaseZero();
case 1:
CaseZeroOrOne();
default:
CaseAny();
}

возникает ошибка времени компиляции. Если после выполнения одного раздела switch должно следовать выполнение другого раздела switch, необходимо явным образом указывать оператор goto case или goto default:

switch (i) {
case 0:
CaseZero();
goto case 1;
case 1:
CaseZeroOrOne();
goto default;
default:
CaseAny();
break;
}

В разделе_switch разрешается использовать несколько меток. Следующий пример является допустимым:

switch (i) {
case 0:
CaseZero();
break;
case 1:
CaseOne();
break;
case 2:
default:
CaseTwo();
break;
}

Здесь правило запрета последовательного выполнения не нарушается, поскольку метки case 2: и default: входят в один раздел_switch.

Правило запрета последовательного выполнения позволяет избежать распространенных ошибок в программах C и C++, вызываемых случайным пропуском оператора break. Кроме того, благодаря этому правилу разделы switch оператора switch можно расставлять в произвольном порядке — это не повлияет на поведение оператора. Например, в вышеприведенном примере можно расположить разделы switch в обратном порядке, и это не отразится на выполнении оператора:

switch (i) {
default:
CaseAny();
break;
case 1:
CaseZeroOrOne();
goto default;
case 0:
CaseZero();
goto case 1;
}

Список операторов раздела switch обычно заканчивается оператором break, goto case или goto default, но в принципе допускается любая конструкция, исключающая достижимость конечной точки списка операторов. Например, оператор while, контролируемый логическим выражением true, никогда не позволит достичь его конечной точки. Аналогично операторы throw и return всегда передают управление в другое место, и их конечные точки также недостижимы. Поэтому следующий пример будет допустимым:

switch (i) {
case 0:
while (true) F();
case 1:
throw new ArgumentException();
case 2:
return;
}

Определяющим типом оператора switch может быть тип string. Например:

void DoCommand(string command) {
switch (command.ToLower()) {
case "run":
DoRun();
break;
case "save":
DoSave();
break;
case "quit":
DoQuit();
break;
default:
InvalidCommand(command);
break;
}
}

Подобно операторам проверки равенства строк (§7.9.7), оператор switch действует без учета регистра символов и сможет выполнить данный раздел switch только при условии, что строка switch-выражения в точности совпадает с константой метки case.

Если определяющим типом оператора switch является string, в качестве константы метки case разрешается использовать значение null.

Списки_операторов в блоке_switch могут включать операторы объявления (§8.5). Областью видимости локальной переменной или константы, объявленной в блоке switch, является этот блок switch.

Значение имени, используемого в контексте выражения, должно быть одинаковым в пределах блока switch (§7.5.2.1).

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

· Значение switch-выражения не является константой.

· Значением switch-выражения является константа, совпадающая с одной из меток case в разделе switch.

· Значением switch-выражения является константа, не совпадающая ни с одной из меток case, и в разделе switch имеется метка default.

· На метку switch раздела switch указывает достижимый оператор goto case или goto default.

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

· Оператор switch содержит достижимый оператор break, осуществляющий выход из оператора switch.

· Оператор switch является достижимым, значение switch-выражения не является константой и метка default отсутствует.

· Оператор switch является достижимым, значением switch-выражения является константа, не совпадающая ни с одной из меток case, и метка default отсутствует.

8.8 Операторы итераций

Оператор итераций повторно выполняет один и тот же внедренный оператор.

оператор_итераций:
оператор_while
оператор_do
оператор_for
оператор_foreach

While

Оператор while выполняет внедренный оператор несколько раз (возможно, ни разу) в зависимости от соблюдения условия.

оператор_while:
while ( логическое_выражение ) внедренное_выражение

Оператор while выполняется следующим образом.

· Вычисляется логическое_выражение (§7.19).

· Если результатом логического выражения является true, управление передается внедренному оператору. Если управление достигает конечной точки внедренного оператора (возможно, в результате выполнения оператора continue), управление передается в начало оператора while.

· Если значением логического выражения является false, управление передается в конечную точку оператора while.

В операторе while внутри внедренного оператора можно использовать оператор break (§8.9.1) для передачи управления в конечную точку оператора while (т. е. для завершения итераций внедренного оператора), а также оператор continue (§8.9.2) для передачи управления в конечную точку внедренного оператора (для выполнения еще одной итерации оператора while).

Внедренный оператор в операторе while считается достижимым, если оператор while является достижимым и значение логического выражения отлично от константы false.

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

· Оператор while содержит достижимый оператор break, осуществляющий выход из оператора while.

· Оператор while является достижимым и значение логического выражения отлично от константы true.

Do

Оператор do выполняет внедренный оператор один или несколько раз в зависимости от соблюдения условия.

оператор_do:
do внедренный_оператор while ( логическое_выражение ) ;

Оператор do выполняется следующим образом.

· Управление передается внедренному оператору.

· Если управление достигает конечной точки внедренного оператора (возможно, в результате выполнения оператора continue), вычисляется логическое_выражение (§7.19). Если результатом логического выражения является true, управление передается в начало оператора do. В противном случае управление передается в конечную точку оператора do.

В операторе do внутри внедренного оператора можно использовать оператор break (§8.9.1) для передачи управления в конечную точку оператора do (т. е. для завершения итераций внедренного оператора), а также оператор continue (§8.9.2) для передачи управления в конечную точку внедренного оператора.

Внедренный оператор в операторе do считается достижимым, если оператор do является достижимым.

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

· Оператор do содержит достижимый оператор break, осуществляющий выход из оператора do.

· Конечная точка внедренного оператора достижима и значение логического выражения отлично от константы true.

For

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

оператор_for:
for ( инициализатор_forнеоб ; условие_forнеоб ; итератор_forнеоб ) внедренный_оператор

инициализатор_for:
объявление_локальной_переменной
список_выражений_операторов

условие_for:
логическое_выражение

итератор_for:
список_выражений_операторов

список_выражений_операторов:
выражение_оператора
список_выражений_операторов , выражение_оператора

Инициализатор_for (если он задан) состоит из объявления_локальной_переменной (§8.5.1) или из списка выражений_операторов (§8.6), разделенных запятыми. Область видимости локальной переменной, объявленной инициализатором_for, начинается с декларатора_локальной_переменной и заканчивается вместе с внедренным оператором. В область видимости включаются условие_for и итератор_for.

Условие_for (если оно задано) должно быть логическим_выражением (§7.19).

Итератор_for (если он задан) состоит из списка выражений_операторов (§8.6), разделенных запятыми.

Оператор for выполняется следующим образом.

· Если задан инициализатор_for, инициализаторы переменных или выражения операторов выполняются в порядке их записи. Этот шаг выполняется только один раз.

· Если задано условие_for, оно проверяется.

· Если условие_for отсутствует или результатом его вычисления является true, управление передается внедренному оператору. Если управление достигает конечной точки внедренного оператора (возможно, в результате выполнения оператора continue), после этого последовательно вычисляются выражения итератора_for (если они заданы), а затем выполняется следующая итерация, начиная с проверки условия_for (см. выше).

· Если условие_for задано и результатом его вычисления является false, управление передается в конечную точку оператора for.

В операторе for внутри внедренного оператора можно использовать оператор break (§8.9.1) для передачи управления в конечную точку оператора for (т. е. для завершения итераций внедренного оператора), а также оператор continue (§8.9.2) для передачи управления в конечную точку внедренного оператора (для выполнения итератора_for и еще одной итерации оператора for, начиная с условия_for).

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

· Оператор for является достижимым и условие_for отсутствует.

· Оператор for является достижимым, условие_for задано и его значение отлично от константы false.

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

· Оператор for содержит достижимый оператор break, осуществляющий выход из оператора for.

· Оператор for является достижимым, условие_for задано и его значение отлично от константы true.

Foreach

оператор_foreach: foreach (… Тип и… Во время…

8.9 Операторы перехода

Операторы перехода осуществляют безусловную передачу управления.

оператор_перехода:
оператор_break
оператор_continue
оператор_goto
оператор_return
оператор_throw

Точка, в которую оператор перехода передает управление, называется его целью.

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

Выполнение оператора перехода усложняется при наличии сопутствующих операторов try. В отсутствие таких операторов try оператор перехода выполняет безусловную передачу управления своей цели. При его использовании вместе с операторами try схема выполнения становится более сложной. Если оператор перехода производит выход из одного или нескольких блоков try, с которыми связаны соответствующие блоки finally, управление вначале передается в блок finally самого внутреннего оператора try. Если управление достигает конечной точки блока finally, после этого управление передается в блок finally следующего объемлющего оператора try. Этот процесс повторяется до тех пор, пока не будут выполнены все блоки finally всех сопутствующих операторов try.

В следующем примере

using System;

class Test
{
static void Main() {
while (true) {
try {
try {
Console.WriteLine("Before break");
break;
}
finally {
Console.WriteLine("Innermost finally block");
}
}
finally {
Console.WriteLine("Outermost finally block");
}
}
Console.WriteLine("After break");
}
}

перед передачей управления в цель оператора перехода выполняются блоки finally, соответствующие двум операторам try.

Вывод выглядит следующим образом:

Before break
Innermost finally block
Outermost finally block
After break

Break

Оператор break осуществляет выход из ближайшего объемлющего оператора switch, while, do, for или foreach.

оператор_break:
break ;

Целью оператора break является конечная точка ближайшего объемлющего оператора switch, while, do, for или foreach. Если оператор break не содержится ни в каком операторе switch, while, do, for или foreach, возникает ошибка времени компиляции.

Если несколько операторов switch, while, do, for или foreach вложены друг в друга, оператор break применяется только к самому внутреннему из них. Для передачи управления с переходом через несколько уровней вложенности следует использовать оператор goto (§8.9.3).

Оператор break не дает возможности выйти из блока finally (§8.10). Если оператор break встречается внутри блока finally, цель оператора break должна находиться в том же блоке finally, в противном случае возникнет ошибка времени компиляции.

Оператор break выполняется следующим образом.

· Если оператор break производит выход из одного или нескольких блоков try, с которыми связаны соответствующие блоки finally, управление вначале передается в блок finally самого внутреннего оператора try. Если управление достигает конечной точки блока finally, после этого управление передается в блок finally следующего объемлющего оператора try. Этот процесс повторяется до тех пор, пока не будут выполнены все блоки finally всех сопутствующих операторов try.

· Управление передается цели оператора break.

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

Continue

оператор_continue: continue ; Целью… Если несколько…

Goto

Оператор goto передает управление оператору, обозначенному меткой.

оператор_goto:
goto идентификатор ;
goto case константное_выражение ;
goto default ;

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

using System;

class Test
{
static void Main(string[] args) {
string[,] table = {
{"Red", "Blue", "Green"},
{"Monday", "Wednesday", "Friday"}
};

foreach (string str in args) {
int row, colm;
for (row = 0; row <= 1; ++row)
for (colm = 0; colm <= 2; ++colm)
if (str == table[row,colm])
goto done;

Console.WriteLine("{0} not found", str);
continue;
done:
Console.WriteLine("Found {0} at [{1}][{2}]", str, row, colm);
}
}
}

оператор goto служит для передачи управления из вложенной области видимости.

Целью оператора goto case является список операторов в непосредственно объемлющем операторе switch (§8.7.2), который содержит метку case с данным константным значением. Если оператор goto case не содержится ни в одном операторе switch, или если константное_выражение не допускает неявное преобразование (§6.1) в определяющий тип ближайшего объемлющего оператора switch, или если ближайший объемлющий оператор switch не содержит метку case с данным константным значением, возникает ошибка времени компиляции.

Целью оператора goto default является список операторов в непосредственно объемлющем операторе switch (§8.7.2), содержащем метку default. Если оператор goto default не содержится ни в одном операторе switch, или если ближайший объемлющий оператор switch не содержит метку default, возникает ошибка времени компиляции.

Оператор goto не дает возможности выйти из блока finally (§8.10). Если оператор goto встречается внутри блока finally, цель оператора goto должна находиться в том же блоке finally, в противном случае возникнет ошибка времени компиляции.

Оператор goto выполняется следующим образом.

· Если оператор goto производит выход из одного или нескольких блоков try, с которыми связаны соответствующие блоки finally, управление вначале передается в блок finally самого внутреннего оператора try. Если управление достигает конечной точки блока finally, после этого управление передается в блок finally следующего объемлющего оператора try. Этот процесс повторяется до тех пор, пока не будут выполнены все блоки finally всех сопутствующих операторов try.

· Управление передается цели оператора goto.

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

Return

Оператор return возвращает управление в программу, вызвавшую функцию-член, в которой используется оператор return.

оператор_return:
return выражениенеоб ;

Оператор return без выражения может использоваться только в функции-члене, не вычисляющей значение, т. е. в методе с типом возвращаемого значения void, методе доступа set для свойства или индексатора, в методах доступа add и remove для события, в конструкторе экземпляров, статическом конструкторе или деструкторе.

Оператор return с выражением может использоваться только в функции-члене, вычисляющей значение, т. е. в методе с типом возвращаемого значения, отличным от void, в методе доступа get для свойства или индексатора или в операторе, определенном пользователем. Должно существовать неявное преобразование (§6.1) типа выражения в тип возвращаемого значения функции-члена, содержащей оператор.

Включение оператора return в блок finally (§8.10) вызывает ошибку времени компиляции.

Оператор return выполняется следующим образом.

· Если оператор return сопровождается выражением, это выражение вычисляется, и результат неявным способом преобразуется в тип возвращаемого значения функции-члена, содержащей оператор. Результат преобразования становится значением, возвращаемым в вызывающую программу.

· Если оператор return входит в один или несколько блоков try, с которыми связаны соответствующие блоки finally, управление вначале передается в блок finally самого внутреннего оператора try. Если управление достигает конечной точки блока finally, после этого управление передается в блок finally следующего объемлющего оператора try. Этот процесс повторяется до тех пор, пока не будут выполнены все блоки finally всех объемлющих операторов try.

· Управление возвращается в вызывающую программу функции-члена, содержащей оператор.

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

Throw

Оператор throw генерирует исключение.

оператор_throw:
throw выражениенеоб ;

Оператор throw с выражением генерирует значение, получаемое в результате вычисления выражения. Выражение должно представлять значение с типом класса System.Exception, или с типом класса, производным от System.Exception, или с типом параметра типа, чьим эффективным базовым классом является System.Exception (или его подкласс). Если результатом вычисления выражения является null, вместо этого генерируется исключение System.NullReferenceException.

Оператор throw без выражения может использоваться только в блоке catch; в этом случае оператор повторно генерирует исключение, уже обрабатываемое этим блоком catch.

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

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

· В текущей функции-члене проверяется каждый оператор try, включающий точку генерации. Для каждого оператора S, начиная с самого внутреннего оператора try и до самого внешнего оператора try, выполняются следующие шаги.

o Если блок try оператора S содержит точку генерации и оператор S содержит одно или несколько предложений catch, эти предложения catch проверяются в порядке их следования в поисках подходящего обработчика для данного исключения. Первое предложение catch, в котором указан тип исключения или базовый тип для типа исключения, считается подходящим вариантом. Предложение catch общего вида (§8.10) считается подходящим для любого типа исключения. Если подходящее предложение catch найдено, распространение исключения завершается передачей управления в блок с этим предложением catch.

o В противном случае, если блок try или блок catch оператора S содержит точку генерации и в операторе S имеется блок finally, управление передается в блок finally. Если блок finally генерирует еще одно исключение, обработка текущего исключения прекращается. В противном случае, когда управление достигает конечной точки блока finally, обработка текущего исключения продолжается.

· Если обработчик исключений не удается найти в ходе текущего вызова функции-члена, этот вызов прекращается. Затем вышеописанные шаги повторяются для программы, вызвавшей функцию-член, но точкой генерации считается оператор, из которого была вызвана функция-член.

· Если обработка исключения приводит к прекращению всех вызовов функций-членов в текущем потоке, свидетельствуя о том, что в этом потоке нет обработчика для исключения, то и сам поток прекращается. Последствия такого прекращения определяются реализацией.

Try

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

оператор_try:
try блок предложения_catch
try блок предложение_finally
try блок предложения_catch предложение_finally

предложения_catch:
специальные_предложения_catch общее_предложение_catchнеоб
специальные_предложения_catchнеоб общее_предложение_catch

специальные_предложения_catch:
специальное_предложение_catch
специальные_предложения_catch специальное_предложение_catch

специальное_предложение_catch:
catch ( тип_класса идентификаторнеоб ) блок

общее_предложение_catch:
catch блок

предложение_finally:
finally блок

Существует три вида операторов try:

· блок try, за которым следует один или несколько блоков catch;

· блок try, за которым следует блок finally;

· блок try, за которым следует один или несколько блоков catch и затем блок finally.

Если в предложении catch указывается тип_класса, это должен быть тип System.Exception, производный тип от System.Exception или тип параметра типа, чей эффективный базовый класс — System.Exception (или его подкласс).

Когда в предложении catch указывается и тип_класса, и идентификатор, тем самым объявляется переменная исключения с данным именем и типом. Переменная исключения соответствует локальной переменной с областью видимости, охватывающей блок catch. В ходе выполнения блока catch переменная исключения представляет обрабатываемое при этом исключение. Для контроля определенного присваивания считается, что в пределах всей области видимости переменной исключения для нее обеспечено определенное присваивание.

Доступ к объекту исключения в блоке catch невозможен, если имя переменной исключения не включено в предложение catch.

Предложение catch, в котором не указан ни тип исключения, ни имя переменной исключения, называется общим предложением catch. Оператор try может содержать только одно общее предложение catch, и если оно имеется, оно должно быть последним предложением catch.

В некоторых языках программирования поддерживаются исключения, которые не могут быть представлены объектами, производными от System.Exception, хотя такие исключения не генерируются кодом C#. Общее предложение catch позволяет перехватывать подобные исключения. Таким образом, общее предложение catch семантически отличается от предложения с указанным типом System.Exception тем, что первое может также перехватывать исключения в программах на других языках.

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

Внутри блока catch с помощью оператора throw (§8.9.5) без выражения можно повторно сгенерировать исключение, перехваченное блоком catch. Присваивание значений переменной исключения не изменяет повторно генерируемого исключения.

В следующем примере

using System;

class Test
{
static void F() {
try {
G();
}
catch (Exception e) {
Console.WriteLine("Exception in F: " + e.Message);
e = new Exception("F");
throw; // re-throw
}
}

static void G() {
throw new Exception("G");
}

static void Main() {
try {
F();
}
catch (Exception e) {
Console.WriteLine("Exception in Main: " + e.Message);
}
}
}

метод F перехватывает исключение, выводит на консоль диагностическую информацию, изменяет переменную исключения и повторно генерирует исключение. Поскольку повторно генерируется исходное исключение, выводится следующее:

Exception in F: G
Exception in Main: G

Если бы первый блок catch сгенерировал исключение e вместо повторения текущего исключения, было бы выведено следующее:

Exception in F: G
Exception in Main: F

Для операторов break, continue и goto передача управления за пределы блока finally вызывает ошибку времени компиляции. Если оператор break, continue или goto включен в блок finally, цель оператора должна находиться в этом же блоке finally, иначе возникнет ошибка времени компиляции.

Кроме того, ошибка времени компиляции распознается при включении оператора return в блок finally.

Оператор try выполняется следующим образом.

· Управление передается в блок try.

· Если управление достигает конечной точки блока try, то:

o если в операторе try имеется блок finally, выполняется блок finally;

o управление передается в конечную точку оператора try.

· Если во время выполнения блока try исключение распространяется в оператор try, происходит следующее.

o Предложения catch, если они имеются, проверяются в порядке их следования в поисках подходящего обработчика для исключения. Первое предложение catch, в котором указан тип исключения или базовый тип для типа исключения, считается подходящим. Общее предложение catch считается подходящим для любого типа исключения. Если подходящее предложение catch найдено, то:

· если в найденном предложении catch объявляется переменная исключения, ей присваивается объект исключения;

· управление передается в найденный блок catch;

· если управление достигает конечной точки блока catch:

o если в операторе try имеется блок finally, выполняется блок finally;

o управление передается в конечную точку оператора try;

· если во время выполнения блока catch исключение распространяется в оператор try:

o если в операторе try имеется блок finally, выполняется блок finally;

o исключение распространяется в следующий объемлющий оператор try.

o Если в операторе try нет предложений catch или ни одно из предложений catch не подходит для исключения, то:

· если в операторе try имеется блок finally, выполняется блок finally;

· исключение распространяется в следующий объемлющий оператор try.

Операторы блока finally всегда выполняются после передачи управления за пределы оператора try. Это правило соблюдается независимо от того, передается ли управление в обычном потоке выполнения, в результате выполнения оператора break, continue, goto или return, или в результате распространения исключения за пределы оператора try.

Если во время выполнения блока finally генерируется исключение, которое не перехватывается в том же блоке finally, исключение распространяется в следующий объемлющий оператор try. Если в этот момент распространялось другое исключение, оно теряется. Процесс распространения исключения рассматривается далее в описании оператора throw (§8.9.5).

Блок try оператора try считается достижимым, если достижим оператор try.

Блок catch оператора try считается достижимым, если достижим оператор try.

Блок finally оператора try считается достижимым, если достижим оператор try.

Конечная точка оператора try достижима, если выполнены следующие два условия:

· достижима конечная точка блока try или по крайней мере одного блока catch;

· достижима конечная точка блока finally, если он имеется.

Checked и unchecked

оператор_checked: checked блок оператор_unchecked: unchecked… Оператор checked задает…

Lock

Оператор lock устанавливает взаимоисключающую блокировку для заданного объекта, выполняет оператор языка и затем снимает блокировку.

оператор_lock:
lock ( выражение ) внедренный_оператор

Выражение оператора lock должно представлять значение, тип которого точно является ссылочным_типом. Для выражения оператора lock никогда не выполняется неявное преобразование с упаковкой (§6.1.7); использование выражения для представления значения, тип которого является типом_значения, вызывает ошибку времени компиляции.

Оператор lock следующего вида:

lock (x) ...

где x — выражение ссылочного_типа, в точности эквивалентен записи

System.Threading.Monitor.Enter(x);
try {
...
}
finally {
System.Threading.Monitor.Exit(x);
}

за исключением того, что x вычисляется только один раз.

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

Не рекомендуется блокировать объекты System.Type для синхронизации доступа к статическим данным. Этот же тип может быть заблокирован другим кодом, что приведет к взаимоблокировке. Синхронизировать доступ к статическим данных лучше путем блокировки закрытого статического объекта. Например:

class Cache
{
private static object synchronizationObject = new object();

public static void Add(object x) {
lock (Cache.synchronizationObject) {
...
}
}

public static void Remove(object x) {
lock (Cache.synchronizationObject) {
...
}
}
}

Using

Оператор using получает один или несколько ресурсов, выполняет заданный оператор языка и затем удаляет ресурсы.

оператор_using:
using ( выделение_ресурса ) внедренный_оператор

выделение_ресурса:
объявление_локальной_переменной
выражение

Ресурс — это класс или структура, реализующая интерфейс System.IDisposable, который состоит из одного метода без параметров с именем Dispose. Код, использующий ресурс, может вызвать метод Dispose, чтобы показать, что ресурс больше не нужен. Если не вызывать метод Dispose, ресурс будет в итоге удален автоматически в результате сборки мусора.

Если выделение_ресурса задано как объявление_локальной_переменной, то типом объявления_локальной_переменной должен быть тип System.IDisposable или тип, допускающий неявное преобразование в System.IDisposable. Если выделение_ресурса задано как выражение, то это должно быть выражение типа System.IDisposable или типа, допускающего неявное преобразование в System.IDisposable.

Локальные переменные, объявленные при выделении_ресурса, доступны только для чтения и должны включать инициализатор. Если внедренный оператор пытается изменить эти локальные переменные (путем присваивания или с помощью операторов ++ и ‑‑), получить их адрес или передать их как параметры ref или out, возникает ошибка времени компиляции.

Процесс оператора using состоит из трех частей: выделение ресурса, использование и удаление. Использование ресурса неявно включается в оператор try с предложением finally. Это предложение finally удаляет ресурс. Если выделяется ресурс null, метод Dispose не вызывается и исключение не генерируется.

Оператор using в виде

using (ResourceType resource = expression) statement

может быть развернут двумя способами. Если ResourceType является типом значения, оператор развертывается так:

{
ResourceType resource = expression;
try {
statement;
}
finally {
((IDisposable)resource).Dispose();
}
}

Если же ResourceType является ссылочным типом, оператор развертывается следующим образом:

{
ResourceType resource = expression;
try {
statement;
}
finally {
if (resource != null) ((IDisposable)resource).Dispose();
}
}

В обоих случаях переменная resource доступна только для чтения во внедренном операторе.

Оператор using в виде

using (expression) statement

может развертываться теми же двумя способами, но на этот раз в качестве ResourceType неявно принимается тип выражения expression, определяемый во время компиляции, а переменная resource недоступна и невидима для внедренного оператора.

Если выделение_ресурса задано в виде объявления_локальной_переменной, можно выделить несколько ресурсов данного типа. Оператор using в виде

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement

в точности эквивалентен последовательности вложенных операторов using:

using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
statement

В следующем примере создается файл log.txt и в него записываются две строки текста. Затем тот же файл открывается для чтения, и содержащиеся в нем строки текста выводятся на консоль.

using System;
using System.IO;

class Test
{
static void Main() {
using (TextWriter w = File.CreateText("log.txt")) {
w.WriteLine("This is line one");
w.WriteLine("This is line two");
}

using (TextReader r = File.OpenText("log.txt")) {
string s;
while ((s = r.ReadLine()) != null) {
Console.WriteLine(s);
}

}
}
}

Поскольку классы TextWriter и TextReader реализуют интерфейс IDisposable, в примере можно использовать оператор using для того, чтобы гарантировать корректное закрытие обрабатываемого файла после операций чтения или записи.

Yield

Оператор yield используется в блоке итератора (§8.2) для выдачи значения в объект перечислителя (§10.14.4) или в перечислимый объект итератора (§10.14.5), либо для сигнализации об окончании итерации.

оператор_yield:
yield return выражение ;
yield break ;

yield не является зарезервированным словом; оно приобретает особый смысл только тогда, когда стоит непосредственно перед ключевым словом return или break. В других контекстах yield может использоваться как идентификатор.

На использование оператора yield накладывается ряд ограничений.

· Использование оператора yield (в любой из двух форм) вне тела_метода, тела_оператора или тела_метода_доступа вызывает ошибку времени компиляции.

· Использование оператора yield (в любой из двух форм) внутри анонимной функции вызывает ошибку времени компиляции.

· Использование оператора yield (в любой из двух форм) в предложении finally оператора try вызывает ошибку времени компиляции.

· Использование оператора yield return в любом месте оператора try, содержащего предложения catch, вызывает ошибку времени компиляции.

В следующем примере демонстрируется несколько допустимых и недопустимых способов использования оператора yield.

delegate IEnumerable<int> D();

IEnumerator<int> GetEnumerator() {
try {
yield return 1; // Правильно
yield break; // Правильно
}
finally {
yield return 2; // Ошибка, yield в предложении finally
yield break; // Ошибка, yield в предложении finally
}

try {
yield return 3; // Ошибка, yield return в блоке try...catch
yield break; // Правильно
}
catch {
yield return 4; // Ошибка, yield return в блоке try...catch
yield break; // Правильно
}

D d = delegate {
yield return 5; // Ошибка, yield в анонимной функции
};
}

int MyMethod() {
yield return 1; // Ошибка, недопустимый тип возвращаемого значения для блока итератора
}

Должно существовать неявное преобразование (§6.1) типа выражения оператора yield return в тип выдачи итератора (§10.14.3).

Оператор yield return выполняется следующим образом.

· Вычисляется выражение, заданное в операторе, оно неявно преобразуется в тип выдачи и присваивается свойству Current объекта перечислителя.

· Выполнение блока итератора приостанавливается. Если оператор yield return входит в один или несколько блоков try, соответствующие блоки finally в этот момент не выполняются.

· Метод MoveNext объекта перечислителя возвращает true в вызвавшую его программу, тем самым указывая на успешный переход объекта перечислителя к следующему элементу.

Следующий вызов метода MoveNext объекта перечислителя возобновляет выполнение блока итератора с того места, где оно было приостановлено.

Оператор yield break выполняется следующим образом.

· Если оператор yield break входит в один или несколько блоков try, с которыми связаны соответствующие блоки finally, управление вначале передается в блок finally самого внутреннего оператора try. Если управление достигает конечной точки блока finally, после этого управление передается в блок finally следующего объемлющего оператора try. Этот процесс повторяется до тех пор, пока не будут выполнены все блоки finally всех объемлющих операторов try.

· Управление возвращается в метод, вызвавший блок итератора. Это либо метод MoveNext, либо метод Dispose объекта перечислителя.

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

9. Пространства имен

Программы на C# организованы с помощью пространств имен. Пространства имен используются как в качестве «внутренней» системы организации для программы, так и в качестве «внешней» системы организации — способа представления программных элементов, предоставляемых другим программам.

Директивы using (§9.4) служат для упрощения использования пространств имен.

9.1 Единицы компиляции

Единица_компиляции определяет общую структуру исходного файла. Единица компиляции состоит из 0 или более директив_using, за которыми следуют 0 или более глобальных_атрибутов, за которыми следуют 0 или более объявлений_членов_пространства_имен.

единица_компиляции:
директивы_extern_aliasнеоб директивы_usingнеоб глобальные_атрибутынеоб
объявления_членов_пространства_именнеоб

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

Директивы_using единицы компиляции влияют на глобальные_атрибуты и объявления_членов_пространства_имен этой единицы компиляции, но не влияют на другие единицы компиляции.

Глобальные_атрибуты (§17) единицы компиляции разрешают спецификацию атрибутов для конечной сборки и модуля. Сборки и модули действуют как физические контейнеры для типов. Сборка может состоять из нескольких физически отдельных модулей.

Объявления_членов_пространства_имен каждой единицы компиляции программы размещают члены в одной области объявления, называемой глобальным пространством имен. Например:

Файл A.cs:

class A {}

Файл B.cs:

class B {}

Эти две единицы компиляции размещаются в одно глобальное пространство имен, в данном случае объявляя два класса с полными именами A и B. Поскольку эти две единицы компиляции размещаются в одну и ту же область объявлений, было бы ошибкой, если бы в каждой из них содержалось объявление члена с одинаковым именем.

9.2 Объявления пространства имен

Объявление_пространства_имен состоит из зарезервированного слова namespace, за которым следует имя и тело пространства имен, а затем точка с запятой (необязательно).

объявление_пространства_имен:
namespace уточненный_идентификатор тело_пространства_имен ;необ

уточненный_идентификатор:
идентификатор
уточненный_идентификатор . идентификатор

тело_пространства_имен:
{ директивы_extern_aliasнеоб директивы_usingнеоб объявления_членов_пространства_именнеоб }

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

Пространства имен являются неявно общедоступными (public); в объявление пространства имен не могут включаться модификаторы доступа.

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

Уточненный_идентификатор объявления_пространства_имен может быть одиночным идентификатором или последовательностью идентификаторов, разделенных маркером «.». Последняя форма позволяет программе определять вложенные пространства имен без лексического вложения нескольких объявлений пространств имен. Например:

namespace N1.N2
{
class A {}

class B {}
}

семантически эквивалентно

namespace N1
{
namespace N2
{
class A {}

class B {}
}
}

Пространства имен являются открытыми, и два объявления пространств имен с одним и тем же полным именем могут размещаться в одну и ту же область объявлений (§3.3). В примере

namespace N1.N2
{
class A {}
}

namespace N1.N2
{
class B {}
}

два объявления пространств имен вверху размещаются в одной и той же области объявлений, объявляя в этом случае два класса с полными именами N1.N2.A и N1.N2.B. Поскольку эти два объявления размещаются в одной и той же области объявлений, было бы ошибкой, если бы в каждом содержалось объявление члена с одним и тем же именем.

9.3 Внешние псевдонимы

Директива_extern_alias вводит идентификатор, служащий псевдонимом пространства имен. Спецификация пространства имен с псевдонимом является внешней по отношению к исходному коду программы и применяется также к вложенным пространствам имен пространства имен с псевдонимом.

директивы_extern_alias:
директива_extern_alias
директивы_extern_alias директива_extern_alias

директива_extern_alias:
extern alias идентификатор ;

Область директивы_extern_alias распространяется на директивы_using, глобальные_атрибуты и объявления_членов_пространства_имен в непосредственно содержащей ее единице компиляции или теле пространства имен.

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

Директива_extern_alias делает псевдоним доступным внутри отдельной единицы компиляции или тела пространства имен, но не размещает новые члены в базовой области объявлений. Иначе говоря, директива_extern_alias не является транзитивной, и влияет только на единицу компиляции или тело пространства имен, в котором находится.

В следующей программе объявляются и используются два внешних псевдонима, X и Y, каждый из которых представляет корень отдельной иерархии пространства имен:

extern alias X;
extern alias Y;

class Test
{
X::N.A a;
X::N.B b1;
Y::N.B b2;
Y::N.C c;
}

В программе объявлено существование внешних псевдонимов X и Y, но фактические определения этих псевдонимов являются внешними для этой программы. На одинаково названные классы N.B теперь можно ссылаться как X.N.B и Y.N.B, или с помощью квалификатора псевдонима пространства имен как X::N.B и Y::N.B. Если в программе объявлен внешний псевдоним, для которого не дано внешнее определение, происходит ошибка.

Using

Директивы using упрощают использование пространств имен и типов, определенных в других пространствах имен. Директивы using влияют на процесс разрешения имен_пространств_имен_или_типов (§3.8) и простых_имен (§7.5.2), но, в отличие от объявлений, директивы using не размещают новые члены в базовых областях объявлений единиц компиляции или пространств имен, в которых они использованы.

директивы_using:
директива_using
директивы_using директива_using

директива_using:
директива_using_alias
директива_using_namespace

Директива_using_alias (§9.4.1) вводит псевдоним для пространства имен или типа.

Директива_using_namespace (§9.4.2) импортирует члены типа пространства имен.

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

Using alias

директива_using_alias: using… Внутри… namespace N1.N2 { class A {} }

Using namespace

директива_using_namespace: using… Внутри… namespace N1.N2 { class A {} }

9.5 Члены пространства имен

Объявление_члена_пространства_имен является либо объявлением_пространства_имен (§9.2), либо объявлением_типа (§9.6).

объявления_членов_пространства_имен:
объявление_члена_пространства_имен
объявления_членов_пространства_имен объявление_члена_пространства_имен

объявление_члена_пространства_имен:
объявление_пространства_имен
объявление_типа

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

9.6 Объявления типов

Объявление_типа является объявлением_класса (§10.1), объявлением_структуры (§11.1), объявлением_интерфейса (§13.1), объявлением_перечисляемого_типа (§14.1) или объявлением_делегата (§15.1).

объявление_типа:
объявление_класса
объявление_структуры
объявление_интерфейса
объявление_перечисляемого_типа
объявление_делегата

Объявление_типа может быть объявлением верхнего уровня в единице компиляции или объявлением члена внутри пространства имен, класса или структуры.

Если объявление типа для типа T является объявлением верхнего уровня в единице компиляции, полное имя вновь объявленного типа просто T. Если объявление типа для типа T находится внутри пространства имен, класса или структуры, то полное имя вновь объявленного типа N.T, где N является полным именем содержащего пространства имен, класса или структуры.

Тип, объявленный внутри класса или структуры, называется вложенным типом (§10.3.8).

Разрешенные модификаторы доступа и доступ по умолчанию для объявления типа зависит от контекста, в котором имеет место объявление (§3.5.1):

· типы, объявленные в единицах компиляции или в пространствах имен, могут иметь доступ public или internal. Доступ по умолчанию internal;

· типы, объявленные в классах, могут иметь доступ public, protected internal, protected, internal или private. Доступ по умолчанию private;

· типы, объявленные в структурах, могут иметь доступ public, internal или private. Доступ по умолчанию private.

9.7 Квалификаторы псевдонима пространства имен

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

Уточненный_член_псевдонима определяется следующим образом:

уточненный_член_псевдонима:
идентификатор :: идентификатор список_аргументов_типанеоб

Уточненный_член_псевдонима можно использовать как имя_пространства_имен_или_типа (§3.8) или как левый операнд доступа_к_члену (§7.5.4).

Уточненный_член_псевдонима может принимать одну из двух форм:

· N::I<A1, ..., AK>, где N и I представляют идентификаторы, а <A1, ..., AK> — список аргументов типа. (K всегда по меньшей мере единица);

· N::I, где N и I представляют идентификаторы. (В этом случае K считается равным нулю.)

При использовании этой нотации значение уточненного_члена_псевдонима определяется следующим образом:

· если N является идентификатором global, то поиск I ведется в глобальном пространстве имен:

o если в глобальном пространстве имен содержится пространство имен с именем I, а K равно нулю, то уточненный_член_псевдонима ссылается на это пространство имен;

o иначе, если в глобальном пространстве имен содержится неуниверсальный тип с именем I, а K равно нулю, то уточненный_член_псевдонима ссылается на этот тип;

o иначе, если в глобальном пространстве имен содержится тип с именем I, у которого имеется K параметров типа, то уточненный_член_псевдонима ссылается на этот тип, сформированный с данными аргументами типа;

o иначе уточненный_член_псевдонима не определен и выдается ошибка времени компиляции.

· иначе, начиная с объявления пространства имен (§9.2), непосредственно содержащего уточненный_член_псевдонима (если имеется), далее для каждого вмещающего объявления пространства имен (если имеются), и заканчивая единицей компиляции, содержащей уточненный_член_псевдонима, оцениваются следующие шаги, пока не будет обнаружена сущность:

o если в объявлении пространства имен или в единице компиляции содержится директива_using_alias, связывающая N с типом, то уточненный_член_псевдонима не определен и выдается ошибка времени компиляции;

o иначе, если в объявлении пространства имен или в единице компиляции содержится директива_extern_alias или директива_using_alias, связывающая N с пространством имен, то:

· если в пространстве имен, связанном с N, содержится пространство имен с именем I, а K равно нулю, то уточненный_член_псевдонима ссылается на это пространство имен;

· иначе, если в пространстве имен, связанном с N, содержится неуниверсальный тип с именем I, а K равно нулю, то уточненный_член_псевдонима ссылается на этот тип;

· иначе, если в пространстве имен, связанном с N, содержится тип с именем I, у которого имеется K параметров типа, то уточненный_член_псевдонима ссылается на этот тип, сформированный с данными аргументами типа;

· иначе уточненный_член_псевдонима не определен и выдается ошибка времени компиляции.

· иначе уточненный_член_псевдонима не определен и выдается ошибка времени компиляции.

Обратите внимание, что использование квалификатора псевдонима пространства имен с псевдонимом, ссылающимся на тип, вызывает ошибку времени компиляции. Кроме того, если N является идентификатором global, то поиск выполняется в глобальном пространстве имен, даже если существует псевдоним using, связывающий global с типом или пространством имен.

9.7.1 Уникальность псевдонимов

В каждой единице компиляции и теле пространства имен имеется отдельная область объявлений для псевдонимов extern и using. Так, хотя имя псевдонима extern или псевдонима using должно быть уникальным внутри набора псевдонимов extern и псевдонимов using, объявленных в непосредственно содержащей единице компиляции или теле пространства имен, псевдоним может иметь то же имя, что и тип или пространство имен, если он используется только с квалификатором :: .

В примере

namespace N
{
public class A {}

public class B {}
}

namespace N
{
using A = System.IO;

class X
{
A.Stream s1; // ошибка: A неоднозначно

A::Stream s2; // ОК
}
}

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


Classes

10.1 Объявления классов.

Объявлением_класса является объявление_типа (§9.6), которое объявляет новый класс.

объявление_класса:
атрибуты (н/о) модификаторы_класса (н/о) partial (н/о) class идентификатор список_параметров_типа (н/о)
база_класса (н/о) предложения_ограничений_параметров_типа (н/о) тело_класса ;(н/о)

Объявление_класса состоит из необязательного набора атрибутов (§17), затем следует необязательный набор модификаторов_класса (§10.1.1), необязательный модификатор partial, ключевое слово class и идентификатор, именующий класс, затем следует необязательный список_параметров_типа (§10.1.3), необязательная спецификация базы_класса (§10.1.4), необязательный набор предложений_ограничений_параметров_типа (§10.1.5), тело_класса (§10.1.6) и необязательная точка с запятой.

Объявление класса не предоставляет предложения_ограничений_параметров_типа, если не предоставляется список_параметров_типа.

Объявление класса, предоставляющее список_параметров_типа, является объявлением универсального класса. Дополнительно любой класс, вложенный в объявление универсального класса или структуру класса, сам является объявлением универсального класса, так как параметры типа для содержащего типа должны быть указаны для создания сформированного типа.

10.1.1 Модификаторы классов.

Объявление_класса может включать последовательность модификаторов класса.

модификаторы_класса:
модификатор_класса
модификаторы_класса модификатор_класса

модификатор_класса:
new
public
protected
internal
private
abstract
sealed
static

Появление в объявлении класса одного и того же модификатора несколько раз является ошибкой времени компилирования.

Модификатор new допускается во вложенных классах. Он определяет скрытие классом унаследованного члена с одинаковым именем согласно описанию в разделе §10.3.4. Появление модификатора new в объявлении класса, не являющимся объявлением вложенного класса, является ошибкой времени компилирования.

Модификаторы public, protected, internal и private управляют доступностью класса. В зависимости от контекста, в котором возникает объявление класса, некоторые из данных модификаторов могут быть запрещены (§3.5.1).

Описание модификаторов abstract, sealed и static представлены в последующих разделах.

10.1.1.1 Абстрактные классы.

Модификатор abstract используется для указания незавершенности класса и необходимости его использования только в качестве базового класса. Ниже представлены отличия абстрактного класса от неабстрактного.

· Абстрактный класс не может быть создан непосредственно, использование оператора new в абстрактном классе является ошибкой времени компилирования. Хотя есть возможность иметь переменные и значения, типами времени компиляции которых являются абстрактные типы, такие переменные и значения будут обязательно иметь значение null или будут содержать ссылки на экземпляры неабстрактных классов, производных от абстрактных типов.

· Абстрактный класс может (но не должен) содержать абстрактные члены.

· Абстрактный класс не может быть запечатанным.

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

abstract class A
{
public abstract void F();
}

abstract class B: A
{
public void G() {}
}

class C: B
{
public override void F() {
// фактическая реализация F
}
}

В этом примере абстрактный класс A представляет абстрактный метод F. Класс B представляет дополнительный метод G, но так как он не предоставляет реализацию F, класс B должен быть также объявлен абстрактным. Класс C переопределяет F и обеспечивает фактическую реализацию. Так как в классе C нет абстрактных членов, класс C может быть (но не должен) быть абстрактным.

10.1.1.2 Запечатанные классы.

Модификатор sealed используется для предотвращения создания из него производных классов. Если запечатанный класс указывается в качестве базового класса другого класса, возникает ошибка времени компилирования.

Запечатанный класс не может быть также абстрактным классом.

Модификатор sealed в первую очередь используется для предотвращения непреднамеренного создания из него производных классов, но также он обеспечивает определенные оптимизации выполнения. В частности, так как запечатанный класс считается не имеющим каких-либо производных классов, можно преобразовать вызовы виртуальной функции-члена экземпляров запечатанного класса в невиртуальные вызовы.

10.1.1.3 Статические классы.

Модификатор static используется для пометки класса, объявленного в качестве статического класса. Для статического класса не может быть создан экземпляр, он не может быть использован в качестве типа и может содержать только статические члены. Только статический класс может содержать объявления методов расширения (§10.6.9).

Объявление статического класса обладает следующими ограничениями.

· Статический класс не может содержать модификатор sealed или abstract. Однако стоит обратить внимание, что, так как для статического класса не может быть создан экземпляр или производный класс, его поведение соответствует поведению запечатанного и абстрактного класса одновременно.

· Статический класс не может содержать спецификацию базы_класса (§10.1.4) и не может явно указывать базовый класс или список реализованных интерфейсов. Статический класс неявно наследуется из типа object.

· Статический класс может содержать только статические члены (§10.3.7). Обратите внимание, что константны и вложенные типы классифицируются как статические члены.

· Статический класс не может содержать члены с объявленной доступностью protected или protected internal.

Нарушение данных ограничений приводит к ошибке времени компилирования.

Статический класс не обладает конструктором экземпляров. Невозможно объявить конструктор экземпляров в статическом классе; конструктор экземпляров по умолчанию (§10.11.4) не предоставляется для статического класса.

Члены статического класса автоматически не являются статическими, а объявления членов должны явно включать модификатор static (исключая константы и вложенные типы). Если класс является вложенным внутри статического внешнего класса, вложенный класс не является статическим классом, если он явно не включает модификатор static.

10.1.1.3.1 Ссылки на типы статического класса.

Ссылка имени_пространства_имен_или_типа на статический класс (§3.8) допускается в следующих случаях.

· Имя_пространства_имен_или_типа T в имени_пространства_имен_или_типа формы T.I или

· Имя_пространства_имен_или_типа T в выражении_typeof (§7.5.11) формы typeof(T).

Ссылка первичного_выражения на статический класс (§7.5) допускается в следующих случаях.

· Первичное_выражение = E в доступе_к_членам (§7.5.4) формы E.I.

В любом другом контексте ссылка на статический класс является ошибкой времени компилирования. Например, ошибкой времени компилирования является использование статического класса в качестве базового класса, составного типа (§10.3.8) члена, аргумента универсального типа или ограничения параметра типа. Аналогичным образом статический класс не может использоваться в типе массива, типе указателя, выражении new, выражении приведения, выражении is, выражении as, выражении sizeof или в выражении значения по умолчанию.

Partial.

Распределение…

10.1.3 Параметры типа.

Параметром типа является простой идентификатор, обозначающий заполнитель для аргумента типа, предоставленного для создания сформированного типа. Параметром типа является формальный заполнитель для типа, предоставляемого позже. И наоборот, аргументом типа (§4.4.1) является фактический тип, заменяемый для параметра типа при создании сформированного типа.

список_параметров_типа:
< параметры_типа >

параметры_типа:
атрибуты (н/о) параметр_типа
параметры_типа , атрибуты (н/о) параметр_типа

параметр_типа:
идентификатор

Каждый параметр типа в объявлении класса определяет имя в области объявления (§3.3) данного класса. Поэтому невозможно задать аналогичное имя для другого параметра типа или члена, объявленного в данном классе. Параметр типа не может иметь одинаковое с самим типом имя.

10.1.4 Спецификация базы класса.

Объявление класса может содержать спецификацию базы_класса, определяющую прямой базовый класс для класса и интерфейсов (§13), реализованных классом.

база_класса:
: тип_класса
: список_типов_интерфейса
: тип_класса , список_типов_интерфейса

список_типов_интерфейса:
тип_интерфейса
список_типов_интерфейсов , тип_интерфейса

Базовый класс, указанный в объявлении класса, может являться типом сформированного класса (§4.4). Базовый класс не может быть параметром типа сам по себе, хотя он может вовлекать параметры типа в области.

class Extend<V>: V {} // Ошибка, параметр типа используется как базовый класс

10.1.4.1 Базовые классы.

Когда тип_класса включен в базу_класса, он определяет прямой базовый класс объявленного класса. Если объявление класса не имеет базы_классы или база_класса перечисляет только типы интерфейса, прямым базовым классом считается object. Класс наследует членов из прямого базового класса (см. раздел §10.3.3).

Пример:

class A {}

class B: A {}

В этом примере класс A считается прямым базовым классом для класса B, а класс B считается производным из класса A. Так как класс A не указывает явно прямой базовый класс, его неявным прямым базовым классом является object.

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

class B<U,V> {...}

class G<T>: B<string,T[]> {...}

базовый класс сформированного типа G<int> должен быть B<string,int[]>.

Прямой базовый класс типа класса должен быть не менее доступен, чем сам тип класса (§3.5.2). Например, если класс public произведен из класса private или internal, возникает ошибка времени компилирования.

Прямой базовый класс типа класса не должен быть одним из следующих типов: System.Array, System.Delegate, System.MulticastDelegate, System.Enum или System.ValueType. Кроме того, объявление универсального класса не может использовать System.Attribute в качестве прямого или непрямого базового класса.

Базовыми классами типа класса являются прямой базовый класс и его базовые классы. Другими словами, набор базовых классов является транзитивным замыканием отношения прямого базового класса. Ссылаясь на пример выше, базовыми классами для класса B являются A и object. Пример:

class A {...}

class B<T>: A {...}

class C<T>: B<IComparable<T>> {...}

class D<T>: C<T[]> {...}

В этом примере базовыми классами D<int> являются C<int[]>, B<IComparable<int[]>>, A и object.

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

Если класс B производится из класса A, возникает ошибка времени компилирования, так как A зависит от B. Класс непосредственно зависит от его прямого базового класса (при его наличии) и от класса, в который он непосредственно вложен (при его наличии). Выражая это в определении, полным набором классов, от которых зависит класс, является транзитивное замыкание отношения непосредственной зависимости.

Пример:

class A: B {}

class B: C {}

class C: A {}

В этом примере имеется ошибка, так как классы циклически зависят друг от друга. Аналогично, в примере

class A: B.C {}

class B: A
{
public class C {}
}

имеется ошибка времени компилирования, так как класс A зависит от класса B.C (его прямой базовый класс), который зависит от класса B (его прямой заключающий класс), который зависит от класса A, создавая циклическую зависимость.

Обратите внимание, что класс не зависит от классов, вложенных в него. Пример:

class A
{
class B: A {}
}

В этом примере B зависит от A (так как A является его прямым базовым классом и прямым заключающим классом), но A не зависит от B (так как B не является базовым классом и заключающим классом A). Поэтому пример работоспособен.

Невозможно выполнить создание производного класса из запечатанного класса sealed. В примере

sealed class A {}

class B: A {} // Ошибка, невозможно создать производный класс из запечатанного класса

класс B является ошибочным, так как он пытается выполнить создание производного класса A из запечатанного класса sealed.

10.1.4.2 Реализации интерфейсов.

Спецификация базы_класса может включать список типов интерфейса, при этом считается, что класс реализует заданные типы интерфейса. Реализация интерфейсов рассмотрена далее в разделе §13.4.

10.1.5 Ограничения параметров типа.

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

предложения_ограничений_параметров_типа:
предложение_ограничений_параметров_типа
предложения_ограничений_параметров_типа предложение_ограничений_параметров_типа

предложение_ограничений параметров_типа:
где параметр_типа : ограничения_параметра_типа

ограничения_параметра_типа:
первичное_ограничение
вторичные_ограничения
ограничение_конструктора
первичное_ограничение , вторичные_ограничения
первичное_ограничение , ограничение_конструктора
вторичные_ограничения , ограничение_конструктора
первичное_ограничение , вторичные_ограничения , ограничение_конструктора

первичное_ограничение:
тип_класса
class
struct

вторичные_ограничения:
тип_интерфейса
параметр_типа
вторичные_ограничения , тип_интерфейса
вторичные_ограничения , параметр_типа

ограничение_конструктора:
new ( )

Каждое предложение_ограничений_параметров_типа состоит из маркера where, следующих затем имени параметра типа, двоеточия и списка ограничений для данного параметра типа. Может существовать не более одного предложения where для каждого параметра типа, порядок перечисления предложений where не имеет существенного значения. Аналогично маркерам get и set в функции доступа к свойству, маркер where не является ключевым словом.

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

Первичное ограничение может быть типом класса или ограничением ссылочного типа class или ограничением типа значения struct. Вторичное ограничение может быть параметром_типа или типом_интерфейса.

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

Ограничение типа значения указывает, что аргумент типа, используемый для параметра типа, не должен быть обнуляемым типом значения. Все необнуляемые типы структур, типы перечислений и параметры типа с ограничением типа значения удовлетворяют данному ограничению. Обратите внимание, что, несмотря на классификацию в качестве типа значения, обнуляемый тип (§4.1.9) не удовлетворяет ограничению типа значения. Параметр типа с ограничением типа значения также не может иметь ограничение_конструктора.

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

Если ограничение является типом класса, типом интерфейса или параметром типа, данный тип указывает минимальный «базовый тип», который должен поддерживать каждый аргумент типа для данного параметра типа. При использовании сформированного типа или универсального метода аргумент типа проверяется на соответствие ограничениям параметра типа во время компилирования. Указанный аргумент типа должен быть произведен из всех ограничений, заданных для данного параметра типа, или должен реализовывать их.

Ограничение типа_класса должно удовлетворять следующим правилам.

· Типом должен быть тип класса.

· Тип не должен быть sealed.

· Тип не должен быть одним из следующих типов: System.Array, System.Delegate, System.Enum или System.ValueType.

· Тип не должен быть object. Так как все типы производятся из object, отсутствие данного ограничения не оказывает никакого влияния.

· Максимум одно ограничение для данного параметра типа может являться типом класса.

Тип, заданный в качестве ограничения тип_интерфейса, должен удовлетворять следующим правилам:

· Он должен быть типом интерфейса.

· Тип не должен быть указан более одного раза в данном предложении where.

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

Любой тип класса или интерфейса, указанный в качестве ограничения параметра типа, должен быть не менее доступен (§3.5.4), чем объявляемый универсальный тип или метод.

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

· Он должен быть параметром типа.

· Тип не должен быть указан более одного раза в данном предложении where.

Кроме того, не допускается наличие циклов в диаграмме зависимости параметров типа, где зависимостью является транзитивное отношение, заданное следующим.

· Если параметр типа T используется в качестве ограничения для параметра типа S, то S зависит от T.

· Если параметр типа S зависит от параметра типа T и T зависит от параметра типа U, то S зависит от U.

Согласно данному утверждению, если параметр типа зависит от самого себя (прямым или косвенным образом), это является ошибкой времени компилирования.

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

· T не должен иметь ограничение типа значения. В противном случае T эффективно запечатывается таким образом, что S будет принудительно того же типа, что и T, устраняя тем самым потребность в двух параметрах типа.

· Если S имеет ограничение типа значения, то T не должен иметь ограничения типа_класса.

· Если S имеет ограничение типа_класса A и T имеет ограничение типа_класса B, то требуется преобразование идентификации или неявные преобразования ссылочных типов из A в B (или из B в A).

· Если S также зависит от параметра типа U, U имеет ограничение типа_класса A и T имеет ограничение типа_класса B, то требуется преобразование идентификации или неявные преобразования ссылочных типов из A в B (или из B в A).

S может иметь ограничение типа значения и T может иметь ограничение ссылочного типа. Фактически это позволяет ограничить T до типов System.Object, System.ValueType, System.Enum и любого типа интерфейса.

Если предложение where для параметра типа включает ограничение конструктора (с формой new()), можно использовать оператор new для создания экземпляров типа (§7.5.10.1). Любой аргумент типа, используемый для параметра типа с ограничением конструктора, должен иметь открытый конструктор без параметров (данный конструктор неявно существует для любого типа значения) или должен быть параметром типа с ограничением типа значения или ограничением конструктора (подробные сведения см. в разделе §10.1.5).

Ниже представлены примеры ограничений.

interface IPrintable
{
void Print();
}

interface IComparable<T>
{
int CompareTo(T value);
}

interface IKeyProvider<T>
{

T GetKey();
}

class Printer<T> where T: IPrintable {...}

class SortedList<T> where T: IComparable<T> {...}

class Dictionary<K,V>
where K: IComparable<K>
where V: IPrintable, IKeyProvider<K>, new()
{
...
}

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

class Circular<S,T>
where S: T
where T: S // Ошибка, цикличность в диаграмме зависимости
{
...
}

Следующие примеры иллюстрируют некоторые недопустимые ситуации.

class Sealed<S,T>
where S: T
where T: struct // Ошибка, T запечатан
{
...
}

class A {...}

class B {...}

class Incompat<S,T>
where S: A, T
where T: B // Ошибка, несовместимые ограничения типа класса
{
...
}

class StructWithClass<S,T,U>
where S: struct, T
where T: U
where U: A // Ошибка, A несовместим со структурой
{
...
}

Эффективный базовый класс параметра типа T определяется следующим образом.

· Если T не имеет первичных ограничений или ограничений параметра типа, его эффективным базовым классом является object.

· Если T имеет ограничения типа значения, его эффективным базовым классом является System.ValueType.

· Если T имеет ограничение типа_класса C, но не имеет ограничений параметра_типа, его эффективным базовым классом является C.

· Если T не имеет ограничение типа_класса, но имеет одно или более ограничений параметра_типа, его эффективным базовым классом является наивысший заключенный тип (§6.4.2) в наборе эффективных базовых классов его ограничений параметра_типа. Правила соответствия обеспечивают существование наивысшего заключенного типа.

· Если T имеет ограничение типа_класса и одно или более ограничений параметра_типа, его эффективным базовым классом является наивысший заключенный тип (§6.4.2) в наборе, состоящем из ограничения типа_класса T и эффективных базовых классов его ограничений параметра_типа. Правила соответствия обеспечивают существование наивысшего заключенного типа.

· Если T имеет ограничение ссылочного типа, но не имеет ограничений типа_класса, его эффективным базовым классом является object.

Эффективный набор интерфейса параметра типа T определяется следующим образом.

· Если T не имеет вторичных_ограничений, его эффективный набор интерфейсов пуст.

· Если T имеет ограничения типа_интерфейса, но не имеет ограничений параметр_ типа, его эффективным набором интерфейса является его набор ограничений типа_интерфейса.

· Если T не имеет ограничений типа_интерфейса, но имеет ограничения параметра_типа, его эффективным набор интерфейса является объединение эффективных наборов интерфейса его ограничений параметра_типа.

· Если T имеет сразу ограничения типа_интерфейса и ограничения параметра_типа, его эффективным набор интерфейса является объединение ограничений типа_интерфейса и эффективных наборов интерфейса его ограничений параметра_типа.

Параметр типа считается ссылочным типом, если он имеет ограничение ссылочного типа, или если его эффективным базовым классом не является object или System.ValueType.

Значения ограниченного типа для типа параметра могут использоваться для доступа к членам экземпляров, подразумеваемых ограничениями. Пример:

interface IPrintable
{
void Print();
}

class Printer<T> where T: IPrintable
{
void PrintOne(T x) {
x.Print();
}
}

В этом примере методы IPrintable могут быть вызваны непосредственно для x, так как T ограничен постоянно реализовывать IPrintable.

10.1.6 Тело класса.

Тело_класса класса определяет членов этого класса.

тело_класса:
{ объявления_члена_классаopt }

10.2 Разделяемые типы.

Объявление типа может быть разделено на несколько объявлений разделяемого типа. Объявление типа формируется из его частей в соответствии с правилами данного раздела, после чего оно считается единым объявлением на протяжении оставшегося времени компилирования и выполнения программы.

Объявление класса, структуры или интерфейса представляет объявление разделяемого типа, если оно включает модификатор partial. Модификатор partial не является ключевым словом и действует в качестве модификатора, только если он расположен непосредственно перед одним из ключевых слов class, struct или interface в объявлении типа или перед типом void в объявлении метода. В других контекстах его можно использовать в качестве нормального идентификатора.

Каждая часть объявления разделяемого типа должна содержать модификатор partial. Он должен иметь такое же имя и должен быть объявлен в том же объявлении пространства имен или типа, что и другие части. Модификатор partial указывает, что могут существовать дополнительные части объявления типа, но их существование не является обязательным; тип с одним объявлением может содержать модификатор partial.

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

Вложенные типы могут быть объявлены в нескольких частях путем использования модификатора partial. Обычно тип-контейнер объявляется также посредством partial, а каждая часть вложенного типа объявляется в другой части типа-контейнера.

Модификатор partial не допускается в объявлениях делегата или перечисляемого типа.

10.2.1 Атрибуты.

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

[Attr1, Attr2("hello")]
partial class A {}

[Attr3, Attr2("goodbye")]
partial class A {}

эквиваленты объявлению:

[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}

Атрибуты параметров типа комбинируются аналогичным образом.

10.2.2 Модификаторы.

Если объявление разделяемого типа включает спецификацию доступности (модификаторы public, protected, internal и private), оно должно быть согласовано со всеми другими частями, включающими спецификацию доступности. Если ни одна из частей разделяемого типа не содержит спецификацию доступности, типу задается соответствующая доступность по умолчанию (§3.5.1).

Если одно или несколько разделяемых объявлений вложенного типа включают модификатор new, предупреждение отсутствует, если вложенный тип скрывает унаследованный член (§3.7.1.2).

Если одно или несколько разделяемых объявлений класса включают модификатор abstract, класс считается абстрактным (§10.1.1.1). В противном случае класс считается неабстрактным.

Если одно или несколько разделяемых объявлений класса включают модификатор sealed, класс считается запечатанным (§10.1.1.2). В противном случае класс считается незапечатанным.

Обратите внимание, что класс не может быть одновременно абстрактным и запечатанным.

При использовании модификатора unsafe в объявлении разделяемого типа только соответствующая часть считается небезопасным контекстом (§18.1).

10.2.3 Параметры и ограничения типа.

Если универсальный тип объявлен в нескольких частях, каждая часть должна формулировать параметры типа. Все части должны иметь одинаковое количество параметров типа и одинаковые имена для каждого параметра типа (по порядку).

Если объявление разделяемого универсального типа содержит ограничения (предложения where), ограничения должны быть согласованы со всеми другими частями, включающими ограничения. В частности, каждая часть, содержащая ограничения, должна иметь ограничения для того же набора параметров типа, а для каждого параметра типа наборы первичных, вторичных ограничений и ограничений конструктора должны быть эквивалентны. Два набора ограничений эквивалентны, если они содержат одинаковые члены. Если ни одна часть разделяемого универсального типа не указывает ограничения параметра типа, параметры типа считаются неограниченными.

Пример:

partial class Dictionary<K,V>
where K: IComparable<K>
where V: IKeyProvider<K>, IPersistable
{
...
}

partial class Dictionary<K,V>
where V: IPersistable, IKeyProvider<K>
where K: IComparable<K>
{
...
}

partial class Dictionary<K,V>
{
...
}

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

10.2.4 Базовый класс.

Если объявление разделяемого класса содержит спецификацию базового класса, требуется согласование со всеми остальными частями, включающими спецификацию базового класса. Если ни одна часть разделяемого класса не включает спецификацию базового класса, базовым классом становится System.Object (§10.1.4.1).

10.2.5 Базовые интерфейсы.

Набором базовых интерфейсов для типа, объявленного в нескольких частях, является объединение базовых интерфейсов, указанных в каждой части. Определенный базовый интерфейс может быть именован только один раз в каждой части, но при этом нескольким частям разрешается именовать соответствующие базовые интерфейсы. Может существовать только одна реализация членов любого заданного базового интерфейса.

В примере

partial class C: IA, IB {...}

partial class C: IC {...}

partial class C: IA, IB {...}

набором базовых интерфейсов для класса C является IA, IB и IC.

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

partial class X
{
int IComparable.CompareTo(object o) {...}
}

partial class X: IComparable
{
...
}

10.2.6 Члены.

Исключая разделяемые методы (§10.2.7), набором членов типа, объявленного в нескольких частях, является просто набор членов, объявленных в каждой части. Тела всех частей объявления типа совместно используют одну область объявления (§3.3), а область каждого члена (§3.7) расширяется на тела всех частей. Домен доступности любого члена всегда содержит все части вмещающего типа; член private, объявленный в одной части, свободно доступен из другой части. Объявление одного члена в нескольких частях типа является ошибкой времени компилирования, если данный член не является типом с модификатором partial.

partial class A
{
int x; // Ошибка, невозможно объявить x более одного раза

partial class Inner // ОК, Inner является разделяемым типом
{
int y;
}
}

partial class A
{
int x; // Ошибка, невозможно объявить x более одного раза

partial class Inner // ОК, Inner является разделяемым типом
{
int z;
}
}

Несмотря на то, что порядок членов в рамках типа несущественен для кода C#, он может быть важным при согласовании с другими языками и средами. В таких случаях порядок членов в рамках типа, объявленного в нескольких частях, не определен.

10.2.7 Разделяемые методы.

Разделяемые методы могут быть заданы в одной части объявления типа и реализованы в другой. Реализация является необязательной. Если ни одна из частей не реализует разделяемый метод, объявление разделяемого метода и все вызовы к нему удаляются из объявления типа, полученного путем комбинирования частей.

Разделяемые методы не могут задавать модификаторы доступа, но неявно являются private. Типом возвращаемого значения разделяемого метода должен быть void, а параметры не могут иметь модификатора out. Идентификатор partial определяется в качестве специального ключевого слова в объявлении метода, только если он появляется непосредственно перед типом void, в противном случае он может использоваться в качестве нормального идентификатора. Разделяемый метод не может явно реализовывать методы интерфейсов.

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

  • Объявления должны иметь одинаковые модификаторы (необязательно в одинаковом порядке), имя метода, количество параметров типа и количество параметров.
  • Соответствующие параметры в объявлениях должны иметь одинаковые модификаторы (необязательно в одном порядке) и одинаковые типы (отличия по модулю в именах параметров типа).
  • Соответствующие параметры типа в объявлениях должны иметь одинаковые константы (отличия по модулю в именах параметров типа).

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

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

Если ни одна из частей объявления разделяемого метода не содержит реализующее объявление для заданного разделяемого метода, любой оператор выражения, вызывающий ее, удаляется из комбинированного объявления типа. Таким образом, выражение вызова, включая любые групповые выражения, не оказывает воздействия во время выполнения. Разделяемый метод сам по себе также удаляется и не будет членом скомбинированного объявления типа.

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

  • Модификатор partial не включается.
  • Атрибутами в результирующем объявлении метода являются скомбинированные атрибуты определяющего и реализующего объявлений разделяемого метода в неопределенном порядке. Дубликаты не удаляются.
  • Атрибутам параметров в результирующем объявлении метода являются скомбинированные атрибуты соответствующих параметров определяющего и реализующего объявлений разделяемого метода в неопределенном порядке. Дубликаты не удаляются.

Если для разделяемого метода M задано определяющее объявление, а реализующее объявление не задано, применяются следующие ограничения.

  • Создание делегата метода является ошибкой времени компилирования (§7.5.10.5).
  • Ссылка на M внутри анонимной функции, преобразованной в тип дерева выражений, является ошибкой времени компилирования (§6.5.2).
  • Выражения, возникающие в качестве части вызова M, не воздействуют на состояние определенного присваивания (§5.3), которое потенциально может привести к ошибкам времени компилирования.
  • Метод M не может являться точкой входа приложения (§3.1).

Разделяемые методы полезны, если одна часть объявления типа должна управлять поведением другой части (например, сгенерированной программным средством). Обратите внимание на следующее объявление разделяемого класса:

partial class Customer
{
string name;

public string Name {

get { return name; }

set {
OnNameChanging(value);
name = value;
OnNameChanged();
}

}

partial void OnNameChanging(string newName);

partial void OnNameChanged();
}

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

class Customer
{
string name;

public string Name {

get { return name; }

set { name = value; }
}
}

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

partial class Customer
{
partial void OnNameChanging(string newName)
{
Console.WriteLine(“Изменение “ + name + “ на “ + newName);
}

partial void OnNameChanged()
{
Console.WriteLine(“Изменено на “ + name);
}
}

Тогда результирующее объявление скомбинированного класса будет эквивалентно следующему:

class Customer
{
string name;

public string Name {

get { return name; }

set {
OnNameChanging(value);
name = value;
OnNameChanged();
}

}

void OnNameChanging(string newName)
{
Console.WriteLine(“Изменение “ + name + “ на “ + newName);
}

void OnNameChanged()
{
Console.WriteLine(“Изменено на “ + name);
}
}

10.2.8 Привязка имен.

Несмотря на то, что каждая часть расширяемого типа должна быть объявлена в одном пространстве имен, части обычно прописываются в рамках различных объявлений пространств имен. Поэтому для каждой части могут присутствовать различные директивы using (§9.4). При преобразовании простых имен (§7.5.2) в рамках одной части учитываются только директивы using объявлениий пространства имен, заключающие данную часть. Это может привести к наличию одинаковых идентификаторов с различными значениями в различных частях.

namespace N
{
using List = System.Collections.ArrayList;

partial class A
{
List x; // x имеет тип System.Collections.ArrayList
}
}

namespace N
{
using List = Widgets.LinkedList;

partial class A
{
List y; // y имеет тип Widgets.LinkedList
}
}

10.3 Члены класса.

Члены класса состоят из членов, представленных его объявлениями_членов_класса и членами, унаследованными от прямого базового класса.

объявления_членов_класса:
объявление_члена_класса
объявления_членов_класса объявление_члена_класса

объявление_члена_класса:
объявление_константы
объявление_поля
объявление_метода
объявление_свойства
объявление_события
объявление_индексатора
объявление_оператора
объявление_конструктора
объявление_деструктора
объявление_статического_конструктора
объявление_типа

Члены типа класса разделены на следующие категории.

· Константы, представляющие постоянные значения, связанные с классом (§10.4).

· Поля, являющиеся переменными класса (§10.5).

· Методы, реализующие вычисления и действия, которые могут быть выполнены классом (§10.6).

· Свойства, определяющие именованные характеристика и действия, связанные с чтением и записью данных характеристик (§10.7).

· События, определяющие уведомления, которые могут быть сгенерированы классом (§10.8).

· Индексаторы, которые обеспечивают индексацию экземпляров класса (синтаксически аналогично индексации массивов) (§10.9).

· Операторы, определяющие операторы выражений, которые могут быть применены к экземплярам класса (§10.9).

· Конструкторы экземпляров, реализующие действия, требуемые для инициализации экземпляров класса (§10.10)

· Деструкторы, реализующие действия, выполняемые до отмены экземпляров класса без возможности восстановления (§10.11).

· Статические конструкторы, реализующие действия, требуемые для инициализации самого класса (§10.12)

· Типы, представляющие локальные типы класса (§10.3.8).

Члены, содержащие исполняемый код, коллективно считаются членами функций типа класса. Членами функций типа класса являются методы, свойства, события, индексаторы, операторы, конструкторы экземпляров, деструкторы и статические конструкторы данного типа класса.

Объявление_класса создает новую область объявления (§3.3), а объявления_члена_класса, непосредственно содержащиеся в объявлении_класса, представляют новых членов в данной области объявления. К объявлениям_члена_класса применяются следующие правила.

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

· Имена константы, поля, свойства, события или типа должны отличаться от имен всех других членов, объявленных в том же классе.

· Имя метода должно отличаться от всех других имен, не относящихся к методам, объявленных в том же классе. Кроме того, сигнатура (§3.6) метода должна отличаться от сигнатур всех других методов, объявленных в том же классе, а два метода, объявленных в одном классе, не могут иметь сигнатуры, отличающиеся только словами ref и out.

· Сигнатура конструктора экземпляра должна отличаться от сигнатур всех других конструкторов экземпляров, объявленных в том же классе, а два конструктора экземпляра, объявленных в одном классе, не могут иметь сигнатуры, отличающиеся только словами ref и out.

· Сигнатура индексатора должна отличаться от сигнатур всех других индексаторов, объявленных в том же классе.

· Сигнатура оператора должна отличаться от сигнатур всех других операторов, объявленных в том же классе.

Унаследованные члены типа класса (§10.3.3) не являются частью области объявления класса. Поэтому производный класс может объявлять члены с именами или сигнатурами, которые уже есть в унаследованном члене (что приводит к скрытию унаследованного члена).

10.3.1 Тип экземпляра.

Каждое объявление класса имеет связанный привязанный тип (§4.4.3) тип экземпляра. Для объявления универсального класса тип экземпляра формируется путем создания сформированного типа (§4.4) из объявления типа с каждым поставленным аргументом типа, являющимся соответствующим параметром типа. Так как тип экземпляра использует параметры типа, он может быть использован только в том случае, когда параметры типа находятся в его области, т. е. в рамках объявления класса. Типом экземпляра является this для кода, написанного внутри объявления класса. Для неуниверсальных классов типом экземпляра является просто объявленный класс. Далее представлены объявления класса с их типами экземпляров.

class A<T> // тип экземпляра: A<T>
{
class B {} // тип экземпляра: A<T>.B

class C<U> {} // тип экземпляра: A<T>.C<U>
}

class D {} // тип экземпляра: D

10.3.2 Члены сформированных типов.

Неунаследованные члены сформированного типа получаются путем замещения для каждого параметра типа в объявлении члена соответствующего аргумента типа сформированного типа. Процесс замещения основан на семантическом значении объявлений типа и не является простым текстовым замещением.

Например, в объявлении универсального класса

class Gen<T,U>
{
public T[,] a;

public void G(int i, T t, Gen<U,T> gt) {...}

public U Prop { get {...} set {...} }

public int H(double d) {...}
}

сформированный тип Gen<int[],IComparable<string>> имеет следующие члены

public int[,][] a;

public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}

public IComparable<string> Prop { get {...} set {...} }

public int H(double d) {...}

Типом члена a в объявлении универсального класса Gen является «двумерный массив T», поэтому типом члена a в сформированном выше типе является «двумерный массив одномерного массива int», или int[,][].

В рамках членов функции экземпляра типом this является тип экземпляра (§10.3.1) содержащего объявления.

Все члены универсального класса могут использовать параметры типа из любого включающего класса непосредственно или в качестве части сформированного типа. Когда определенный закрытый сформированный тип (§4.4.2) используется во время выполнения, каждое использование параметра типа заменяется фактическим аргументом типа, предоставляемым сформированному типу. Например:

class C<V>
{
public V f1;
public C<V> f2 = null;

public C(V x) {
this.f1 = x;
this.f2 = this;
}
}

class Application
{
static void Main() {
C<int> x1 = new C<int>(1);
Console.WriteLine(x1.f1); // Выводится 1

C<double> x2 = new C<double>(3.1415);
Console.WriteLine(x2.f1); // Выводится 3.1415
}
}

10.3.3 Наследование.

Класс наследует члены своего прямого типа базового класса. Наследование означает, что класс неявно содержит все члены его прямого типа базового класса, исключая конструкторы экземпляров, деструкторы и статические конструкторы базового класса. Далее перечислены некоторые важные аспекты наследования.

· Наследование транзитивно. Если C произведено из B, а B произведено из A, то C унаследует как члены, объявленные в B, так и члены, объявленные в A.

· Производный класс расширяет свой прямой базовый класс. Производный класс может добавлять новые члены к своим наследователям, но он не может удалить определение унаследованного члена.

· Конструкторы экземпляров, деструкторы и статические конструкторы не наследуются, но все другие члены, независимо от их объявленной доступности, наследуются (§3.5). Однако, в зависимости от их объявленной доступности, унаследованные члены могут быть недоступны в производном классе.

· Производный класс может скрывать (§3.7.1.2) унаследованные члены путем объявления новых членов с тем же именем или сигнатурой. Обратите внимание, что скрытие унаследованного члена не приводит к удалению данного члена, оно просто приводит к тому, что этот член становится недоступен непосредственно из производного класса.

· Экземпляр класса содержит набор всех полей экземпляра, объявленных в классе и его базовом классе, а также неявное преобразование (§6.1.6) из типа производного класса в любой из его типов базового класса. Таким образом, ссылка на экземпляр некоторого производного класса обрабатывается как ссылка на экземпляр какого-либо из его базовых классов.

· Класс может объявить виртуальные методы, свойства и индексаторы, а производный класс может переопределить реализацию данных членов функций. Это позволяет классам реализовывать полиморфное поведение, при котором действия, выполняемые вызовом члена функции, отличаются в зависимости от типа времени выполнения экземпляра, в котором вызывается член функции.

Унаследованным членом типа сформированного класса являются члены непосредственного типа базового класса (§10.1.4.1), который обеспечивается путем замещения аргументов типа сформированного типа для каждого появления соответствующих параметров типа в спецификации_базового_класса. В свою очередь, данные члены преобразуются путем замещения для каждого параметра_типа в объявлении члена соответствующего аргумента_типа_спецификации_базового_класса.

class B<U>
{
public U F(long index) {...}
}

class D<T>: B<T[]>
{
public T G(string s) {...}
}

В вышеуказанном примере сформированный тип D<int> имеет неунаследованный член public int G(string s), получаемый путем замещения аргумента типа int для типа параметра T. D<int> также имеет унаследованный член из объявления класса B. Данный унаследованный член определяется путем определения типа базового класса B<int[]> из D<int> посредством замещения int для T в спецификации базового класса B<T[]>. После этого в качестве аргумента типа B int[] замещается для U в public U F(long index), что приводит к унаследованному члену public int[] F(long index).

New.

Для объявления_члена_класса допускается объявление члена с одинаковым именем или сигнатурой унаследованного члена. При этом член производного класса скрывает член базового класса. Скрытие унаследованного члена не считается ошибкой, но приводит к отображению компилятором предупреждения. Чтобы эти предупреждения не появлялись, объявление члена производного класса может включать модификатор new для указания того, что производный член намеренно скрывает базовый член. Данная тема более подробно рассматривается в разделе §3.7.1.2.

Если модификатор new включен в объявление, не скрывающее унаследованный член, создается предупреждение об этом эффекте. Данное предупреждение можно отключить путем удаления модификатора new.

10.3.5 Модификаторы доступа.

Объявление_класса_доступа может иметь любой из пяти возможных видов объявленной доступности (§3.5.1): public, protected internal, protected, internal или private. Исключая комбинацию protected internal, указание более одного модификатора доступа является ошибкой времени компилирования. Если объявление_члена_класса не включает какие-либо модификаторы доступа, предполагается использование модификатора private.

10.3.6 Составные типы.

Типы, используемые в объявлении члена, являются составными типами данного члена. Возможные составные члены — это типы константы, поля, свойства, события или индексатора, тип возвращаемого значения метода или оператора и типы параметров метода, индексатора, оператора или конструктора экземпляров. Составные типы члена должны быть не менее доступны, чем сам член (§3.5.4).

10.3.7 Статические члены и члены экземпляра.

Члены класса являются либо статическими членами, либо членами экземпляра. В общем случае считается, что статические члены принадлежат к типу класса, а члены экземпляра принадлежат к объектам (экземплярам типов класса).

Когда объявление поля, метода, свойства, события, оператора или конструктора содержит модификатор static, оно объявляет статический член. Кроме того, объявление константы или типа неявно объявляет статический член. Статические члены имеют следующие особенности.

· Когда на статический член M ссылаются в доступе_члена (§7.5.4) формы E.M, E должен обозначать тип, содержащий M. Обозначение E экземпляром является ошибкой времени компилирования.

· Статическое поле определяет только одно место хранения для всех экземпляров указанного закрытого типа класса. Вне зависимости от количества создаваемых экземпляров закрытого типа класса всегда существует только одна копия статического поля.

· Член статической функции (метод, свойство, событие, оператор или конструктор) не выполняет операции с конкретным экземпляром, при использовании зарезервированного слова this в члене функции возникает ошибка компиляции.

Когда объявление поля, метода, свойства, события, индексатора, конструктора или деструктора не содержит модификатор static, оно объявляет член экземпляра. (Член экземпляра иногда называют нестатическим членом.) Члены экземпляра имеют следующие особенности.

· Когда на член экземпляра M ссылаются в доступе_члена (§7.5.4) формы E.M, E должен обозначать экземпляр тип-контейнера M. Обозначение E типом является ошибкой времени компилирования.

· Каждый экземпляр класса содержит отдельный набор всех полей экземпляра класса.

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

В следующем примере представлены правила доступа к статическим членам и членам экземпляров:

class Test
{
int x;
static int y;

void F() {
x = 1; // OК, аналог this.x = 1
y = 1; // OК, аналог Test.y = 1
}

static void G() {
x = 1; // Ошибка, невозможно вызвать this.x
y = 1; // OК, аналог Test.y = 1
}

static void Main() {
Test t = new Test();
t.x = 1; // OК
t.y = 1; // Ошибка, невозможно вызвать статический член через экземпляр
Test.x = 1; // Ошибка, невозможно вызвать член экземпляра через тип
Test.y = 1; // OК
}
}

Метод F указывает, что в члене функции экземпляра простое_имя (§7.5.2) может использоваться для доступа к членам экземпляров и статическим членам. Метод G указывает, что вызов члена экземпляра через простое_имя в статическом члене функции является ошибкой времени компилирования. Метод Main указывает, что в доступе_члена (§7.5.4) члены экземпляра должны вызываться через экземпляры, а статические члены должны вызываться через типы.

10.3.8 Вложенные типы.

Тип, объявленный в рамках объявления класса или структуры, называется вложенным типом. Тип, объявленный в рамках единицы компиляции или пространства имен, называется невложенным типом.

Пример:

using System;

class A
{
class B
{
static void F() {
Console.WriteLine("A.B.F");
}
}
}

В этом примере класс B является вложенным типом, так как он объявлен в рамках класса A, а класс A является невложенным типом, так как он объявлен в рамках единицы компиляции.

10.3.8.1 Полные имена.

Полным именем (§3.8.1) для вложенного типа является S.N, где S является полным именем типа, в котором объявлен тип N.

10.3.8.2 Объявленная доступность

Невложенные типы могут иметь объявленную доступность public или internal, а по умолчанию – internal. Вложенные типы тоже могут иметь эти виды объявленной доступности, плюс один или более видов объявленной доступности, в зависимости от того, является ли тип-контейнер классом или структурой:

· вложенный тип, объявленный в классе, может иметь любой из пяти видов объявленной доступности (public, protected internal, protected, internal или private) и, подобно другим членам класса, по умолчанию – private;

· вложенный тип, объявленный в структуре, может иметь любой из трех видов объявленной доступности (public, internal или private) и, подобно другим членам структуры, по умолчанию – private.

В примере

public class List
{
// Структура частных данных
private class Node
{
public object Data;
public Node Next;

public Node(object data, Node next) {
this.Data = data;
this.Next = next;
}
}

private Node first = null;
private Node last = null;

// Общий интерфейс

public void AddToFront(object o) {...}

public void AddToBack(object o) {...}

public object RemoveFromFront() {...}

public object RemoveFromBack() {...}

public int Count { get {...} }
}

объявлен частный вложенный класс Node.

10.3.8.3 Скрытие

Вложенный тип может скрывать (§3.7.1) базовый член. В объявлениях вложенных типов разрешен модификатор new, так что скрытие можно выразить явно. В примере

using System;

class Base
{
public static void M() {
Console.WriteLine("Base.M");
}
}

class Derived: Base
{
new public class M
{
public static void F() {
Console.WriteLine("Derived.M.F");
}
}
}

class Test
{
static void Main() {
Derived.M.F();
}
}

показан вложенный класс M, скрывающий метод M, определенный в Base.

This

Вложенный тип и его тип-контейнер не имеют особого отношения к доступу_this (§7.5.7). В частности, this внутри вложенного типа нельзя использовать для ссылки на члены экземпляра типа-контейнера. В случае если вложенному типу требуется доступ к членам экземпляра типа-контейнера, доступ может быть предоставлен подачей this для экземпляра типа-контейнера в качестве аргумента конструктора для вложенного типа. В следующем примере

using System;

class C
{
int i = 123;

public void F() {
Nested n = new Nested(this);
n.G();
}

public class Nested
{
C this_c;

public Nested(C c) {
this_c = c;
}

public void G() {
Console.WriteLine(this_c.i);
}
}
}

class Test
{
static void Main() {
C c = new C();
c.F();
}
}

показан этот способ. Экземпляр C создает экземпляр Nested и передает свой собственный this конструктору Nested, чтобы предоставить последовательный доступ к членам экземпляра C.

10.3.8.5 Доступ к частным и защищенным членам типа-контейнера

Вложенный тип имеет доступ ко всем членам, к которым есть доступ из типа-контейнера, в том числе к членам типа-контейнера с объявленной доступностью private и protected. В примере

using System;

class C
{
private static void F() {
Console.WriteLine("C.F");
}

public class Nested
{
public static void G() {
F();
}
}
}

class Test
{
static void Main() {
C.Nested.G();
}
}

показан класс C, содержащий вложенный класс Nested. Внутри Nested метод G вызывает статический метод F, определенный в C, а F имеет частную объявленную доступность.

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

using System;

class Base
{
protected void F() {
Console.WriteLine("Base.F");
}
}

class Derived: Base
{
public class Nested
{
public void G() {
Derived d = new Derived();
d.F(); // ОК
}
}
}

class Test
{
static void Main() {
Derived.Nested n = new Derived.Nested();
n.G();
}
}

вложенный класс Derived.Nested обращается к защищенному методу F, определенному в Base, базовом классе класса Derived, вызовом через экземпляр класса Derived.

10.3.8.6 Вложенные типы в универсальных классах

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

Каждое объявление типа, содержащееся внутри объявления универсального типа, неявно является объявлением универсального типа. При записи обращения к типу, вложенному в универсальный тип, необходимо указать сформированный тип-контейнер, включая его аргументы типа. Однако изнутри внешнего класса вложенный тип можно использовать без уточнения; тип экземпляра внешнего класса может быть неявно использован при формировании вложенного типа. В следующем примере показаны три разных правильных способа обращения к сформированному типу, созданному из Inner; первые два способа эквивалентны:

class Outer<T>
{
class Inner<U>
{
public static void F(T t, U u) {...}
}

static void F(T t) {
Outer<T>.Inner<string>.F(t, "abc"); // Эти два оператора имеют
Inner<string>.F(t, "abc"); // одинаковый результат

Outer<int>.Inner<string>.F(3, "abc"); // Это другой тип

Outer.Inner<string>.F(t, "abc"); // Ошибка, для Outer требуется аргумент типа
}
}

Хотя это плохой стиль программирования, параметр типа во вложенном типе может скрывать член или параметр типа, объявленный во внешнем типе:

class Outer<T>
{
class Inner<T> // Допустимо, скрывает T в Outer
{
public T t; // Обращается к T в Inner
}
}

10.3.9 Зарезервированные имена членов

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

Зарезервированные имена не вводят объявления, и таким образом не участвуют в поиске членов. Но подписи зарезервированного метода, связанные с объявлением, все же участвуют в наследовании (§10.3.3) и могут быть скрыты с помощью модификатора new (§10.3.4).

Резервирование этих имен служит трем целям:

· разрешить лежащей в основе реализации использовать обычный идентификатор в качестве имени метода для получения или установки доступа к средствам языка C#;

· разрешить другим языкам возможность взаимодействия, используя обычный идентификатор в качестве имени метода для получения или установки доступа к средствам языка C#;

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

Объявление деструктора (§10.13) также вызывает резервирование подписи (§10.3.9.1).

10.3.9.1 Имена членов, зарезервированные для свойств

Для свойства P (§10.7) типа T зарезервированы следующие подписи:

T get_P();
void set_P(T value);

Зарезервированы обе подписи, даже если свойство только для чтения или только для записи.

В примере

using System;

class A
{
public int P {
get { return 123; }
}
}

class B: A
{
new public int get_P() {
return 456;
}

new public void set_P(int value) {
}
}

class Test
{
static void Main() {
B b = new B();
A a = b;
Console.WriteLine(a.P);
Console.WriteLine(b.P);
Console.WriteLine(b.get_P());
}
}

класс A определяет свойство P только для чтения, резервируя тем самым подписи для методов get_P и set_P. Класс B производится от A и скрывает обе эти зарезервированные подписи. В результате получается:

123
123
456

10.3.9.2 Имена членов, зарезервированные для событий

Для события E (§10.8) типа делегата T зарезервированы следующие подписи:

void add_E(T handler);
void remove_E(T handler);

10.3.9.3 Имена членов, зарезервированные для индексаторов

Для индексатора (§10.9) типа T со списком параметров L зарезервированы следующие подписи:

T get_Item(L);
void set_Item(L, T value);

Зарезервированы обе подписи, даже если индексатор только для чтения или только для записи.

10.3.9.4 Имена членов, зарезервированные для деструкторов

Для класса, содержащего деструктор (§10.13), зарезервирована следующая подпись:

void Finalize();

10.4 Константы

Константа – это член класса, представляющий постоянное значение: значение, которое может быть вычислено во время компиляции. Объявление_константы вводит одну или более констант указанного типа.

объявление_константы:
атрибутынеоб модификаторы_константнеоб const тип деклараторы_констант ;

модификаторы_констант:
модификатор_константы
модификаторы_констант модификатор_константы

модификатор_константы:
public
protected
internal
private

деклараторы_констант:
декларатор_константы
деклараторы_констант , декларатор_константы

декларатор_константы:
идентификатор = константное_выражение

Объявление_константы может включать набор атрибутов (§17), модификатор new (§10.3.4) и допустимое сочетание из четырех модификаторов доступа (§10.3.5). Атрибуты и модификаторы применяются ко всем членам, объявленным объявлением_константы. Хотя константы считаются статическими членами, объявление_константы не требует и не допускает модификатор static. Неоднократное появление одного и того же модификатора в объявлении константы является ошибкой.

Тип в объявлении_константы указывает тип членов, вводимых объявлением. За типом следует список деклараторов_констант, каждый из которых вводит новый член. Декларатор_константы состоит из идентификатора, именующего член, за которым следует лексема «=», а затем константное_выражение (§7.18), задающее значение члена.

Тип, указанный в объявлении константы, должен быть типом sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, перечисляемым_типом или ссылочным_типом. Каждое константное_выражение должно давать значение конечного типа или типа, который может быть неявно преобразован к конечному типу (§6.1).

Тип константы должен быть по крайней мере таким же доступным, как сама константа (§3.5.4).

Значение константы получается в выражении с помощью простого_имени (§7.5.2) или доступа_к_члену (§7.5.4).

Константа сама может участвовать в константном_выражении. Таким образом, константу можно использовать в любой конструкции, где требуется константное_выражение. Примеры таких конструкций включают метки case, операторы goto case, объявления членов enum, атрибуты и другие объявления констант.

Как описано в §7.18, константное_выражение является выражением, которое может быть полностью вычислено во время компиляции. Поскольку единственный способ создать отличное от null значение ссылочного_типа, отличное от string, – применить оператор new, а оператор new не допускается в константном выражении, единственным возможным значением для констант ссылочных типов, отличных от string, является null.

Если для значения константы требуется символическое имя, а тип этого значения не допускается в объявлении константы, или если значение невозможно вычислить во время компиляции посредством константного_выражения, вместо этого можно использовать поле readonly (§10.5.2).

Объявление константы, объявляющее несколько констант, эквивалентно нескольким объявлениям одной константы с теми же атрибутами, модификаторами и типом. Пример

class A
{
public const double X = 1.0, Y = 2.0, Z = 3.0;
}

эквивалентен

class A
{
public const double X = 1.0;
public const double Y = 2.0;
public const double Z = 3.0;
}

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

class A
{
public const int X = B.Z + 1;
public const int Y = 10;
}

class B
{
public const int Z = A.Y + 1;
}

компилятор сначала вычисляет A.Y, затем B.Z и наконец A.X, создавая значения 10, 11 и 12. Объявления констант могут зависеть от констант из других программ, но такие зависимости возможны тоько в одном направлении. В вышеприведенном примере: если бы A и B были объявлены в отдельных программах, для A.X было бы возможно зависеть от B.Z, но тогда B.Z не мог бы одновременно зависеть от A.Y.

10.5 Поля

Поле – это член, представляющий переменную, связанную с объектом или классом. Объявление_поля вводит одно или более полей заданного типа.

объявление_поля:
атрибутынеоб модификаторы_полянеоб тип деклараторы_переменных ;

модификаторы_поля:
модификатор_поля
модификаторы_поля модификатор_поля

модификатор_поля:
new
public
protected
internal
private
static
readonly
volatile

деклараторы_переменных:
декларатор_переменной
деклараторы_переменных , декларатор_переменной

декларатор_переменной:
идентификатор
идентификатор = инициализатор_переменной

инициализатор_переменной:
выражение
инициализатор_массива

Объявление_поля может включать набор атрибутов (§17), модификатор new (§10.3.4), допустимое сочетание из четырех модификаторов доступа (§10.3.5) и модификатор static (§10.5.1). Кроме того, объявление_поля может включать модификатор readonly (§10.5.2) или volatile (§10.5.3), но не оба. Атрибуты и модификаторы применяются ко всем членам, объявленным объявлением_поля. Неоднократное появление одного и того же модификатора в объявлении поля является ошибкой.

Тип в объявлении_поля указывает тип членов, вводимых объявлением. За типом следует список деклараторов_переменных, каждый из которых вводит новый член. Декларатор_переменной состоит из идентификатора, именующего член, за которым необязательно следуют лексема «=» и инициализатор_переменной (§10.5.5), задающий начальное значение члена.

Тип поля должен быть по крайней мере таким же доступным, как само поле (§3.5.4).

Значение поля получается в выражении с помощью простого_имени (§7.5.2) или доступа_к_члену (§7.5.4). Значение поля с доступом не только для чтения изменяется с помощью присваивания (§7.16). Значение поля с доступом не только для чтения можно и получить, и изменить с помощью операторов постфиксного увеличения и уменьшения (§7.5.9) и операторов префиксного увеличения и уменьшения (§7.6.5).

Объявление поля, объявляющее несколько полей, эквивалентно нескольким объявлениям одного поля с теми же атрибутами, модификаторами и типом. Пример

class A
{
public static int X = 1, Y, Z = 100;
}

эквивалентен

class A
{
public static int X = 1;
public static int Y;
public static int Z = 100;
}

10.5.1 Статические поля и поля экземпляров

Если объявление поля включает модификатор static, поля, введенные этим объявлением, являются статическими полями. Если нет модификатора static, введенные объявлением поля являются полями экземпляров. Статические поля и поля экземпляров – это два из нескольких видов переменных (§5), поддерживаемых C#, и иногда их называют статическими переменными и переменными экземпляров, соответственно.

Статическое поле не является частью определенного экземпляра, а используется совместно всеми экземплярами закрытого типа (§4.4.2). Независимо от того, сколько создано экземпляров закрытого типа, всегда существует только одна копия статического поля для соответствующей области приложения.

Пример.

class C<V>
{
static int count = 0;

public C() {
count++;
}

public static int Count {
get { return count; }
}
}

 

class Application
{
static void Main() {
C<int> x1 = new C<int>();
Console.WriteLine(C<int>.Count); // Печатает 1

C<double> x2 = new C<double>();
Console.WriteLine(C<int>.Count); // Печатает 1

C<int> x3 = new C<int>();
Console.WriteLine(C<int>.Count); // Печатает 2
}
}

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

Когда к полю обращаются через доступ_к_члену (§7.5.4) вида E.M, если M является статическим полем, E должно означать тип, содержащий M, а если M является полем экземпляра, E должно означать экземпляр типа, содержащего M.

Различия между статическими членами и членами экземпляров обсуждаются далее в §10.3.7.

10.5.2 Поля только для чтения

Если объявление_поля включает модификатор readonly, поля, введенные этим объявлением, являются полями только для чтения. Прямые присваивания полям только для чтения могут быть только или как часть этого объявления, или в конструкторе экземпляров, или в статическом конструкторе в этом же классе. (Присваивание полям только для чтения в этих контекстах можно делать многократно.) А именно, прямые присваивания полю readonly разрешены только в следующих контекстах:

· в деклараторе_переменной, который вводит это поле (включением в объявление инициализатора_переменной);

· для поля экземпляра в конструкторах экземпляров того класса, который содержит объявление поля; для статического поля в статическом конструкторе того класса, который содержит объявление поля. Это также единственные контексты, в которых допускается передача поля readonly в качестве параметров out или ref.

Попытка присваивания полю readonly или передачи его в качестве параметра out или ref в любом другом контексте является ошибкой времени компиляции.

10.5.2.1 Использование статических полей только для чтения вместо констант

Поле static readonly полезно, если требуется символическое имя для значения константы, но тип значения недопустим в объявлении const, или если значение не может быть вычислено во время компиляции. В примере

public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);

private byte red, green, blue;

public Color(byte r, byte g, byte b) {
red = r;
green = g;
blue = b;
}
}

члены Black, White, Red, Green и Blue нельзя объявить как const, так как их значения нельзя вычислить во время компиляции. Зато объявление их как static readonly имеет почти такой же результат.

10.5.2.2 Отслеживание версий констант и статических полей только для чтения

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

using System;

namespace Program1
{
public class Utils
{
public static readonly int X = 1;
}
}

namespace Program2
{
class Test
{
static void Main() {
Console.WriteLine(Program1.Utils.X);
}
}
}

Пространства имен Program1 и Program2 обозначают две программы, компилируемые отдельно. Так как Program1.Utils.X объявлено как статическое поле только для чтения, выходное значение оператора Console.WriteLine не известно во время компиляции, но получается во время выполнения. Таким образом, если X изменен и Program1 перекомпилирована, оператор Console.WriteLine выведет новое значение, даже если Program2 не перекомпилирована. Но если бы X был константой, значение X было бы получено при компиляции Program2 и осталось бы независимым от изменений в Program1 до перекомпиляции Program2.

Volatile

Способы оптимизации,… · чтение поля… · запись поля volatile…

10.5.4 Инициализация поля

Начальным значением поля, как статического, так и поля экземпляра, является значение по умолчанию (§5.2) типа поля. Невозможно видеть значение поля до выполнения этой инициализации по умолчанию, и таким образом поле никогда не бывает «неинициализированным». Пример

using System;

class Test
{
static bool b;
int i;

static void Main() {
Test t = new Test();
Console.WriteLine("b = {0}, i = {1}", b, t.i);
}
}

дает в результате

b = False, i = 0

так как и b, и i автоматически инициализированы значениями по умолчанию.

10.5.5 Инициализаторы переменных

Объявление поля может включать инициализаторы_переменных. Для статических полей инициализаторы переменных соответствуют операторам присваивания, выполняемым во время инициализации класса. Для полей экземпляров инициализаторы переменных соответствуют операторам присваивания, выполняемым при создании экземпляра класса.

Пример

using System;

class Test
{
static double x = Math.Sqrt(2.0);
int i = 100;
string s = "Hello";

static void Main() {
Test a = new Test();
Console.WriteLine("x = {0}, i = {1}, s = {2}", x, a.i, a.s);
}
}

дает в результате

x = 1.4142135623731, i = 100, s = Hello

так как присваивание для x происходит при выполнении инициализаторов статического поля, а присваивания для i и s происходят при выполнении инициализаторов поля экземпляра.

Инициализация значения по умолчанию, описанная в §10.5.4, происходит для всех полей, включая поля, имеющие инициализаторы переменных. Таким образом, когда класс инициализируется, все статические поля в этом классе сначала инициализируются своими значениями по умолчанию, а затем выполняются инициализаторы статических полей в текстовом порядке. Аналогично, когда создается экземпляр класса, все поля экземпляров в этом экземпляре сначала инициализируются своими значениями по умолчанию, а затем выполняются инициализаторы полей экземпляров в текстовом порядке.

Можно видеть статические поля с инициализаторами переменных в их состоянии со значениями по умолчанию. Однако такой стиль настоятельно не рекомендуется. Пример

using System;

class Test
{
static int a = b + 1;
static int b = a + 1;

static void Main() {
Console.WriteLine("a = {0}, b = {1}", a, b);
}
}

показывает такое поведение. Несмотря на циклические определения a и b, программа допустима. Она дает на выходе

a = 1, b = 2

так как статические поля a и b инициализированы значением 0 (значение по умолчанию для int) до выполнения их инициализаторов. Когда выполняется инициализатор для a, значение b равно нулю, так что a инициализируется значением 1. Когда выполняется инициализатор для b, значение a уже равно 1, так что b инициализируется значением 2.

10.5.5.1 Инициализация статического поля

Инициализаторы переменных статических полей класса соответствуют последовательности присваиваний, выполняемых в текстовом порядке, в котором они появляются в объявлении класса. Если в классе существует статический конструктор (§10.12), выполнение инициализаторов статического поля происходит непосредственно перед выполнением этого статического конструктора. Иначе инициализаторы статического поля выполняются в зависящий от реализации момент перед первым использованием статического поля этого класса. Пример

using System;

class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}

public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}

class A
{
public static int X = Test.F("Init A");
}

class B
{
public static int Y = Test.F("Init B");
}

может дать на выходе либо:

Init A
Init B
1 1

либо:

Init B
Init A
1 1

так как выполнение инициализатора X и инициализатора Y могло произойти в том или другом порядке; они только должны быть выполнены до обращений к этим полям. Однако в примере:

using System;

class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}

public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}

class A
{
static A() {}

public static int X = Test.F("Init A");
}

class B
{
static B() {}

public static int Y = Test.F("Init B");
}

результатом должно быть:

Init B
Init A
1 1

так как правила выполнения статических конструкторов (как определено в §10.12) обеспечивают, что статический конструктор для B (и следовательно инициализаторы статического поля B) должен выполняться до статического конструктора и инициализаторов поля A.

10.5.5.2 Инициализация поля экземпляра

Инициализаторы переменных полей экземпляров класса соответствуют последовательности присваиваний, выполняемых непосредственно при входе в любой из конструкторов экземпляров (§10.11.1) этого класса. Инициализаторы переменных выполняются в том текстовом порядке, в каком они появляются в объявлении класса. Создание экземпляра класса и процесс инициализации описаны далее в §10.11.

Инициализатор переменной для поля экземпляра не может обращаться к создаваемому экземпляру. Таким образом, обращение к this в инициализаторе переменной, как и обращение инициализатора переменной к любому члену экземпляра по простому_имени, является ошибкой времени компиляции. В примере

class A
{
int x = 1;
int y = x + 1; // Ошибка, ссылка на член экземпляра с this
}

инициализатор переменной для y приводит к ошибке времени компиляции, так как ссылается на член создаваемого экземпляра.

10.6 Методы

Метод – это член, реализующий вычисление или действие, которое может быть выполнено объектом или классом. Методы объявляются с помощью объявлений_методов:

объявление_метода:
заголовок_метода тело_метода

заголовок_метода:
атрибутынеоб модификаторы_методанеоб partialнеоб тип_возвращаемого_значения имя_члена список_параметров_типанеоб
( список_формальных_параметровнеоб ) предложения_ограничений_параметров_типанеоб

модификаторы_метода:
модификатор_метода
модификаторы_метода модификатор_метода

модификатор_метода:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

тип_возвращаемого_значения:
тип
void

имя_члена:
идентификатор
тип_интерфейса . идентификатор

тело_метода:
блок
;

Объявление_метода может включать набор атрибутов (§17) и допустимое сочетание из четырех модификаторов доступа (§10.3.5), и модификаторы new (§10.3.4), static (§10.6.2), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6) и extern (§10.6.7).

Объявление имеет допустимое сочетание модификаторов, если верно все следующее:

· объявление включает допустимое сочетание модификаторов доступа (§10.3.5);

· объявление не включает один и тот же модификатор несколько раз;

· объявление включает не более одного из следующих модификаторов: static, virtual и override;

· объявление включает не более одного из следующих модификаторов: new и override;

· если объявление включает модификатор abstract, то объявление не включает ни один из следующих модификаторов: static, virtual, sealed и extern;

· если объявление включает модификатор private, то объявление не включает ни один из следующих модификаторов: virtual, override и abstract;

· если объявление включает модификатор sealed, то объявление также включает модификатор override;

· если объявление включает модификатор partial, то оно не включает ни один из следующих модификаторов: new, public, protected, internal, private, virtual, sealed, override, abstract и extern.

Тип_возвращаемого_значения в объявлении метода указывает тип значения, вычисляемого и возвращаемого методом. Если метод не возвращает значение, типом_возвращаемого_значения является void. Если объявление включает модификатор partial, типом возвращаемого значения должен быть void.

Имя_члена указывает имя метода. Если только метод не является членом явной реализации интерфейса (§13.4.1), имя_члена – это просто идентификатор. Для члена явной реализации интерфейса имя_члена состоит из типа_интерфейса, за которым следует «.» и идентификатор.

Необязательный список_параметров_типа указывает параметры типа метода (§10.1.3). Если список параметров типа указан, метод является универсальным методом. Если у метода есть модификатор extern, список_параметров_типа указывать нельзя.

Необязательный список_формальных_параметров указывает параметры метода (§10.6.1).

Необязательные предложения_ограничений_параметров_типов указывают ограничения по отдельным параметрам типов (§10.1.5) и могут быть указаны только если предоставлен также список_параметров_типов, а у метода нет модификатора override.

Тип_возвращаемого_значения и каждый из типов в списке_формальных_параметров метода должен быть по крайней мере так же доступным, как сам метод (§3.5.4).

Для методов abstract и extern тело_метода состоит просто из точки с запятой. Для методов partial тело_метода может состоять либо из точки с запятой, либо из блока. Для всех других методов тело_метода состоит из блока, в котором указаны операторы для исполнения при вызове метода.

Имя, список типов параметров и список формальных параметров метода определяют подпись (§3.6) метода. А именно, подпись метода состоит из его имени, числа параметров типов и номера, модификаторов и типов его формальных параметров. Для этих целей каждый параметр типа метода, встречающийся в типе формального параметра, идентифицируется не своим именем, а своим порядковым номером в списке аргументов типа метода. Тип возвращаемого значения не является частью подписи метода, как и имена параметров типов и формальных параметров.

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

Типы_параметров метода находятся в области видимости всего объявления_метода и могут использоваться для формирования типов в этой области в типе_возвращаемого_значения, теле_метода и в предложениях_ограничений_типов_параметров, но не в атрибутах.

Все формальные параметры и параметры типов должны иметь разные имена.

10.6.1 Параметры метода

Параметры метода, если они имеются, объявляются списком_формальных_параметров метода.

список_формальных_параметров:
фиксированные_параметры
фиксированные_параметры , массив_параметров
массив_параметров

фиксированные_параметры:
фиксированный_параметр
фиксированные_параметры , фиксированный_параметр

фиксированный_параметр:
атрибутынеоб модификатор_параметранеоб тип идентификатор

модификатор_параметра:
ref
out
this

массив_параметров:
атрибутынеоб params тип_массива идентификатор

Список формальных параметров состоит из одного или более параметров, разделенных запятыми, из которых только последний может быть массивом_параметров.

Фиксированный_параметр состоит из необязательного набора атрибутов (§17), необязательного модификатора ref, out или this, типа и идентификатора. Каждый фиксированный_параметр объявляет параметр заданного типа с заданным именем. Модификатор this определяет метод как метод расширения и допускается только в первом параметре статического метода. Методы расширения описаны далее в §10.6.9.

Массив_параметров состоит из необязательного набора атрибутов (§17), модификатора params, типа_массива и идентификатора. Массив параметров объявляет единственный параметр заданного типа массива с заданным именем. Тип_массива массива параметров должен быть типом одномерного массива (§12.1). При вызове метода массив параметров допускает указание либо одного аргумента с данным типом массива, либо ноль или более аргументов типа элемента массива. Параметры массивов описаны далее в §10.6.1.4.

Объявление метода создает отдельную область объявлений для параметров, параметров типов и локальных переменных. Имена вводятся в эту область объявлений списком параметров типов и списком формальных параметров метода, и объявлениями локальных переменных в блоке метода. Одно и то же имя для двух членов области объявлений метода является ошибкой. Содержание элементов с одним и тем же именем в области объявлений метода и в области объявлений локальных переменных вложенной области объявлений является ошибкой.

Вызов метода (§7.5.5.1) создает определенную для этого вызова копию формальных параметров и локальных переменных метода, а список аргументов вызова присваивает значения или ссылки на переменные вновь созданным формальным параметрам. Внутри блока метода к формальным параметрам можно обращаться по их идентификаторам в выражениях простого_имени (§7.5.2).

Есть четыре вида формальных параметров:

· параметры по значению, объявляемые без модификаторов;

· параметры по ссылке, объявляемые с модификатором ref;

· выходные параметры, объявляемые с модификатором out;

· массивы параметров, объявляемые с модификатором params.

Как описано в §3.6, модификаторы ref и out являются частью подписи метода, а модификатор params – нет.

10.6.1.1 Параметры по значению

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

Если формальный параметр является параметром по значению, соответствующий аргумент в вызове метода должен быть выражением типа, неявно преобразуемого (§6.1) к типу формального параметра.

Методу разрешено присваивать новые значения параметру по значению. Такие присваивания влияют только на локальное место хранения, представленное параметром по значению—они не влияют на фактический аргумент, заданный при вызове метода.

10.6.1.2 Параметры по ссылке

Параметр, объявленный с модификатором ref, является параметром по ссылке. В отличие от параметра по значению, параметр по ссылке не создает новое место хранения. Вместо этого параметр по ссылке представляет то же самое место хранения, что переменная, заданная в качестве аргумента при вызове метода.

Если формальный параметр является параметром по ссылке, соответствующий аргумент в вызове метода должен состоять из зарезервированного слова ref, за которым следует ссылка_на_переменную (§5.3.3) того же типа, что и формальный параметр. Переменная должна быть определенно назначенной до того, как ее можно будет передать в качестве параметра по ссылке.

Внутри метода параметр по ссылке всегда считается определенно назначенным.

У метода, объявленного как итератор (§10.14), не может быть параметров по ссылке.

Пример

using System;

class Test
{
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}

static void Main() {
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine("i = {0}, j = {1}", i, j);
}
}

дает в результате

i = 2, j = 1

При вызове метода Swap из Main x представляет i, а y представляет j. Таким образом, результатом вызова является обмен значений i и j.

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

class A
{
string s;

void F(ref string a, ref string b) {
s = "One";
a = "Two";
b = "Three";
}

void G() {
F(ref s, ref s);
}
}

вызов F из G передает ссылку на s и для a, и для b. Таким образом, при этом вызове все имена s, a и b ссылаются на одно и то же место хранения, а все три присваивания изменяют поле экземпляра s.

10.6.1.3 Выходные параметры

Параметр, объявленный с модификатором out, является выходным параметром. Подобно параметру по ссылке, выходной параметр не создает новое место хранения. Вместо этого выходной параметр представляет то же самое место хранения, что и переменная, заданная в качестве аргумента при вызове метода.

Если формальный параметр является выходным параметром, соответствующий аргумент в вызове метода должен состоять из зарезервированного слова out, за которым следует ссылка_на_переменную (§5.3.3) того же типа, что и формальный параметр. Переменной не требуется быть определенно назначенной, прежде чем ее можно будет передать в качестве выходного параметра, но вслед за вызовом, в котором переменная была передана в качестве выходного параметра, эта переменная считается определенно назначенной.

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

Каждый выходной параметр метода должен быть определенно назначен до возврата из метода.

Метод, объявленный как частичный (§10.2.7) или как итератор (§10.14), не может иметь выходные параметры.

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

using System;

class Test
{
static void SplitPath(string path, out string dir, out string name) {
int i = path.Length;
while (i > 0) {
char ch = path[i – 1];
if (ch == '\\' || ch == '/' || ch == ':') break;
i--;
}
dir = path.Substring(0, i);
name = path.Substring(i);
}

static void Main() {
string dir, name;
SplitPath("c:\\Windows\\System\\hello.txt", out dir, out name);
Console.WriteLine(dir);
Console.WriteLine(name);
}
}

В результате получается:

c:\Windows\System\
hello.txt

Обратите внимание, что переменным dir и name может быть не сделано присваивание до их передачи в SplitPath, но они считаются определенно назначенными вслед за вызовом.

10.6.1.4 Массивы параметров

Параметр, объявленный с модификатором params, является массивом параметров. Если список формальных параметров включает массив параметров, тот должен быть последним параметром в списке и должен иметь тип одномерного массива. Например, типы string[] и string[][] можно использовать как тип массива параметров, но тип string[,] – нельзя. Не допускается сочетание модификатора params с модификаторами ref и out.

Массив параметров позволяет указывать аргументы одним из двух способов при вызове метода:

· аргумент, заданный для массива параметров, может быть одним выражением такого типа, который неявно преобразуем (§6.1) к типу массива параметров. В таком случае массив параметров действует точно так же, как параметр по значению;

· или же в вызове можно указать ноль или более аргументов для массива параметров, где каждый аргумент является выражением такого типа, который неявно преобразуем (§6.1) к типу элемента массива параметров. В таком случае вызов создает экземпляр типа массива параметров с длиной, соответствующей числу аргументов, инициализирует элементы экземпляра массива заданными значениями аргументов и использует вновь созданный экземпляр массива в качестве фактического аргумента.

Кроме разрешения переменного числа аргументов при вызове, массив параметров совершенно эквивалентен параметру по значению (§10.6.1.1) того же типа.

Пример

using System;

class Test
{
static void F(params int[] args) {
Console.Write("Массив содержит {0} элемента:", args.Length);
foreach (int i in args)
Console.Write(" {0}", i);
Console.WriteLine();
}

static void Main() {
int[] arr = {1, 2, 3};
F(arr);
F(10, 20, 30, 40);
F();
}
}

дает в результате

Массив содержит 3 элемента: 1 2 3
Массив содержит 4 элемента: 10 20 30 40
Массив содержит 0 элементов:

При первом вызове F просто передает массив a в качестве параметра по значению. При втором вызове F автоматически создает четырехэлементный массив int[] с заданными значениями элементов и передает этот массив в качестве параметра по значению. Аналогично, третий вызов F создает массив из нуля элементов int[] и передает этот экземпляр в качестве параметра по значению. Второй и третий вызовы совершенно эквивалентны записи:

F(new int[] {10, 20, 30, 40});
F(new int[] {});

При выполнении разрешения перегрузки метод с массивом параметров может применяться либо в его обычном виде, либо в расширенном виде (§7.4.3.1). Расширенный вид метода доступен только если неприменим обычный вид, и только если в том же типе уже не объявлен метод с такой же подписью, как у расширенного вида.

Пример

using System;

class Test
{
static void F(params object[] a) {
Console.WriteLine("F(object[])");
}

static void F() {
Console.WriteLine("F()");
}

static void F(object a0, object a1) {
Console.WriteLine("F(object,object)");
}

static void Main() {
F();
F(1);
F(1, 2);
F(1, 2, 3);
F(1, 2, 3, 4);
}
}

дает в результате

F();
F(object[]);
F(object,object);
F(object[]);
F(object[]);

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

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

В примере

using System;

class Test
{
static void F(params object[] args) {
foreach (object o in args) {
Console.Write(o.GetType().FullName);
Console.Write(" ");
}
Console.WriteLine();
}

static void Main() {
object[] a = {1, "Привет", 123.456};
object o = a;
F(a);
F((object)a);
F(o);
F((object[])o);
}
}

получаются следующие выходные данные

System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double

При первом и последнем вызове функции F применима нормальная форма F, поскольку существует неявное преобразование из типа аргумента к типу параметра (оба имеют тип object[]). Таким образом, в результате разрешения перегрузки выбирается нормальная форма F, а аргумент передается как регулярный параметр значения. При втором и третьем вызове нормальная форма F неприменима, поскольку не существует неявного преобразования из типа аргумента к типу параметра (тип object не может быть неявно преобразован к типу object[]). Однако в этом случае применима расширенная форма F, которая выбирается в результате разрешения перегрузки. В результате вызова создается массив object[], который содержит один элемент, инициализируемый с помощью заданного значения аргумента (это значение представляет собой ссылку на object[]).

10.6.2 Статические методы и методы экземпляра

Статический метод объявляется с помощью модификатора static. Если модификатор static отсутствует, метод является методом экземпляра.

Статический метод не выполняет операций с конкретным экземпляром. При использовании зарезервированного слова this в статическом методе возникает ошибка компиляции.

Метод экземпляра выполняет операции с конкретным экземпляром класса, обращение к которому осуществляется с помощью зарезервированного слова this (§7.5.7).

При доступе_к_члену (§7.5.4) с использованием ссылки на метод в форме E.M для статического метода M параметр E должен означать тип, содержащий M, а для метода экземпляра M — экземпляр типа, содержащего M.

Различия между статическими членами и членами экземпляров рассматриваются в разделе §10.3.7.

10.6.3 Виртуальные методы

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

Реализация невиртуального метода инвариантна и одинакова вне зависимости от того, вызывается ли метод для экземпляра класса, в котором он объявлен, или для экземпляра производного класса. Напротив, реализация виртуального метода может быть заменена в производных классах. Процесс замены реализации унаследованного виртуального метода называется переопределением этого метода (§10.6.4).

При вызове виртуального метода тип времени выполнения экземпляра, для которого осуществляется вызов, определяет фактическую реализацию вызываемого метода. При вызове невиртуального метода определяющим фактором является тип времени компиляции экземпляра. В частности, при вызове метода N со списком аргументов A для экземпляра, имеющего тип времени компиляции C и тип времени выполнения R (здесь R представляет собой C или производный от C класс), процесс вызова осуществляется следующим образом.

· Сначала к C, N и A применяется разрешение перегрузки для выбора конкретного метода M из набора методов, объявленных и унаследованных классом C. Этот процесс описывается в разделе §7.5.5.1.

· Затем, если M является невиртуальным методом, вызывается метод M.

· В противном случае M является виртуальным методом, и вызывается старшая производная реализация метода M по отношению к R.

Для каждого виртуального метода, объявленного в классе или унаследованного им, существует старшая производная реализация метода по отношению к этому классу. Старшая производная реализация виртуального метода M по отношению к классу R определяется следующим образом.

· Если R содержит представляющее объявление virtual метода M, это является старшей производной реализацией M.

· В противном случае, если R содержит объявление override метода M, это является старшей производной реализацией M.

· В противном случае старшей производной реализацией метода M по отношению к R является старшая производная реализация M по отношению к прямому базовому классу R.

В следующем примере показаны различия между виртуальными и невиртуальными методами:

using System;

class A
{
public void F() { Console.WriteLine("A.F"); }

public virtual void G() { Console.WriteLine("A.G"); }
}

class B: A
{
new public void F() { Console.WriteLine("B.F"); }

public override void G() { Console.WriteLine("B.G"); }
}

class Test
{
static void Main() {
B b = new B();
A a = b;
a.F();
b.F();
a.G();
b.G();
}
}

В этом примере класс A представляет невиртуальный метод F и виртуальный метод G. Класс B представляет новый невиртуальный метод F, скрывая тем самым унаследованную функцию F, а также переопределяет унаследованный метод G. После выполнения примера получается следующий результат:

A.F
B.F
B.G
B.G

Обратите внимание, что оператор a.G() вызывает метод B.G, а не A.G. Это связано с тем, что фактически вызываемая реализация метода определяется типом времени выполнения (B), а не типом времени компиляции экземпляра (A).

Поскольку методам разрешено скрывать унаследованные методы, класс может содержать несколько виртуальных методов с одинаковыми подписями. При этом не возникает проблема неоднозначности, поскольку видимым является только старший производный метод. В примере

using System;

class A
{
public virtual void F() { Console.WriteLine("A.F"); }
}

class B: A
{
public override void F() { Console.WriteLine("B.F"); }
}

class C: B
{
new public virtual void F() { Console.WriteLine("C.F"); }
}

class D: C
{
public override void F() { Console.WriteLine("D.F"); }
}

class Test
{
static void Main() {
D d = new D();
A a = d;
B b = d;
C c = d;
a.F();
b.F();
c.F();
d.F();
}
}

классы C и D содержат два виртуальных метода с одинаковыми подписями, один из которых представлен классом A, а второй — классом C. Метод, представленный классом C, скрывает метод, унаследованный из класса A. Таким образом, объявление переопределения в классе D переопределяет метод, представленный классом C. При этом в классе D невозможно переопределить метод, представленный классом A. После выполнения примера получается следующий результат:

B.F
B.F
D.F
D.F

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

10.6.4 Переопределяющие методы

Если объявление метода экземпляра содержит модификатор override, метод является переопределяющим методом. Такой метод переопределяет унаследованный виртуальный метод с такой же подписью. Объявление виртуального метода определяет новый метод. Объявление переопределяющего метода уточняет существующий виртуальный метод, предоставляя его новую реализацию.

Метод, переопределяемый с помощью объявления override, называется переопределенным базовым методом. Для переопределяющего метода M, объявленного в классе C, переопределенный базовый метод определяется посредством проверки всех базовых для C типов класса. Проверка начинается с прямого базового типа класса C и продолжается для каждого последующего прямого базового типа класса. Проверка продолжается до тех пор, пока не будет найден базовый тип класса, содержащий хотя бы один доступный метод M, подпись которого после замены аргументов типа соответствует заданной. Переопределенный базовый метод считается доступным, если он объявлен как public, protected, protected internal или объявлен как internal в той же программе, что и C.

Если для объявления переопределения не выполняются все следующие условия, возникает ошибка времени компиляции.

· Может быть найден переопределенный базовый метод (см. выше).

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

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

· Переопределенный базовый метод не является запечатанным методом.

· Переопределяющий метод и переопределенный базовый метод имеют одинаковые типы возвращаемых значений.

· Для объявления переопределения и переопределенного базового метода объявлен одинаковый уровень доступа. Другими словами, объявление переопределения не может изменять доступность виртуального метода. Однако если переопределенный базовый метод объявлен как protected internal в сборке, отличной от той, в которой содержится переопределяющий метод, для последнего следует объявить уровень доступа protected.

· Объявление переопределения не содержит предложений, ограничивающих параметры типа. Все ограничения наследуются из переопределенного базового метода.

В следующем примере показано применение правил переопределения для универсальных классов:

abstract class C<T>
{
public virtual T F() {...}

public virtual C<T> G() {...}

public virtual void H(C<T> x) {...}
}

class D: C<string>
{
public override string F() {...} // Допустимо

public override C<string> G() {...} // Допустимо

public override void H(C<T> x) {...} // Ошибка. Следует использовать C<string>
}

class E<T,U>: C<U>
{
public override U F() {...} // Допустимо

public override C<U> G() {...} // Допустимо

public override void H(C<T> x) {...} // Ошибка. Следует использовать C<U>
}

Объявление переопределения может обращаться к переопределенному базовому методу посредством базового_доступа (§7.5.8). В примере

class A
{
int x;

public virtual void PrintFields() {
Console.WriteLine("x = {0}", x);
}
}

class B: A
{
int y;

public override void PrintFields() {
base.PrintFields();
Console.WriteLine("y = {0}", y);
}
}

с помощью метода base.PrintFields() класса B вызывается метод PrintFields, объявленный в классе A. При базовом_доступе отключается виртуальный механизм вызова, в результате чего базовый метод рассматривается как невиртуальный. Если записать вызов в классе B как ((A)this).PrintFields(), это приведет к рекурсивному вызову метода PrintFields, объявленного в классе B, а не в классе A. Это связано с тем, что метод PrintFields является виртуальным, а типом времени выполнения для ((A)this) является B.

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

class A
{
public virtual void F() {}
}

class B: A
{
public virtual void F() {} // Предупреждение. Скрывается унаследованный метод F()
}

метод F класса B не содержит модификатор override и, следовательно, не переопределяет метод F класса A. В этом случае метод F класса B скрывает метод класса A, и отображается предупреждение, поскольку объявление не содержит модификатор new.

В примере

class A
{
public virtual void F() {}
}

class B: A
{
new private void F() {} // Скрывает метод A.F в теле класса B
}

class C: B
{
public override void F() {} // Допустимо. Переопределяет метод A.F
}

метод F класса B скрывает виртуальный метод F, унаследованный из класса A. Поскольку для нового метода F класса B объявлен уровень доступа private, область его действия распространяется только на тело класса B и не включает класс C. Таким образом, объявление метода F в классе C может переопределять метод, F унаследованный из класса A.

10.6.5 Запечатанные методы

Если объявление метода экземпляра содержит модификатор sealed, метод является запечатанным методом. Если объявление метода содержит модификатор sealed, в него также необходимо включить модификатор override. Использование модификатора sealed позволяет предотвратить последующее переопределение метода в производных классах.

В примере

using System;

class A
{
public virtual void F() {
Console.WriteLine("A.F");
}

public virtual void G() {
Console.WriteLine("A.G");
}
}

class B: A
{
sealed override public void F() {
Console.WriteLine("B.F");
}

override public void G() {
Console.WriteLine("B.G");
}
}

class C: B
{
override public void G() {
Console.WriteLine("C.G");
}
}

класс B предоставляет два переопределяющих метода: метод F с модификатором sealed и метод G без него. Использование модификатора sealed в классе B позволяет предотвратить последующее переопределение метода F в классе C.

10.6.6 Абстрактные методы

Если объявление метода экземпляра содержит модификатор abstract, метод является абстрактным методом. Поскольку абстрактный метод неявно также является виртуальным, он не может содержать модификатор virtual.

Объявление абстрактного метода представляет новый виртуальный метод, но не предоставляет его реализацию. Вместо этого необходимо определить неабстрактные производные классы, предоставляющие собственные реализации этого метода посредством его переопределения. Поскольку абстрактный метод не предоставляет фактическую реализацию, тело_абстрактного_метода состоит только из точки с запятой.

Объявления абстрактных методов допускаются только в абстрактных классах (§10.1.1.1).

В примере

public abstract class Shape
{
public abstract void Paint(Graphics g, Rectangle r);
}

public class Ellipse: Shape
{
public override void Paint(Graphics g, Rectangle r) {
g.DrawEllipse(r);
}
}

public class Box: Shape
{
public override void Paint(Graphics g, Rectangle r) {
g.DrawRect(r);
}
}

класс Shape определяет абстрактное представление геометрической фигуры самоокрашивающегося объекта. Метод Paint является абстрактным, поскольку для него не отсутствует значащая реализация по умолчанию. Классы Ellipse и Box представляют собой конкретные реализации метода Shape. Поскольку эти классы не являются абстрактными, в них необходимо переопределить метод Paint и предоставить его фактическую реализацию.

При ссылке на абстрактный метод посредством базового_доступа (§7.5.8) возникает ошибка времени компиляции. В примере

abstract class A
{
public abstract void F();
}

class B: A
{
public override void F() {
base.F(); // Ошибка. Метод base.F является абстрактным
}
}

при вызове метода base.F() выполняется ссылка на абстрактный метод, что порождает ошибку времени компиляции.

В объявлении абстрактного метода допускается переопределение виртуального метода. Это позволяет абстрактному классу принудительно выполнить повторную реализацию метода в производных классах. При этом исходная реализация метода становится недоступна. В примере

using System;

class A
{
public virtual void F() {
Console.WriteLine("A.F");
}
}

abstract class B: A
{
public abstract override void F();
}

class C: B
{
public override void F() {
Console.WriteLine("C.F");
}
}

в классе A объявляется виртуальный метод, который переопределяется в классе B абстрактным методом. Последний переопределяется в классе C с использованием собственной реализации.

10.6.7 Внешние методы

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

Модификатор extern обычно используется совместно с атрибутом DllImport (§17.5), что позволяет реализовывать внешние методы с помощью библиотек динамической компоновки (DLL). Среда выполнения может поддерживать другие механизмы реализации внешних методов.

Если внешний метод содержит атрибут DllImport, объявление метода также должно включать модификатор static. В следующем примере показано использование модификатора extern совместно с атрибутом DllImport:

using System.Text;
using System.Security.Permissions;
using System.Runtime.InteropServices;

class Path
{
[DllImport("kernel32", SetLastError=true)]
static extern bool CreateDirectory(string name, SecurityAttribute sa);

[DllImport("kernel32", SetLastError=true)]
static extern bool RemoveDirectory(string name);

[DllImport("kernel32", SetLastError=true)]
static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);

[DllImport("kernel32", SetLastError=true)]
static extern bool SetCurrentDirectory(string name);
}

10.6.8 Разделяемые методы

Если объявление метода содержит модификатор partial, метод является разделяемым методом. Разделяемые методы могут объявляться только в качестве членов разделяемых типов (§10.2). На использование таких методов накладывается ряд ограничений. Дополнительные сведения о разделяемых методах см. в §10.2.7.

10.6.9 Методы расширения

Если первый параметр метода содержит модификатор this, такой метод называется методом расширения. Методы расширения могут быть объявлены только в статических классах, не являющихся универсальными или вложенными. Первый параметр метода расширения не может содержать отличных от this модификаторов или иметь тип указателя.

В следующем примере показан статический класс, в котором объявляется два метода расширения:

public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}

public static T[] Slice<T>(this T[] source, int index, int count) {
if (index < 0 || count < 0 || source.Length – index < count)
throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}

Метод расширения представляет собой регулярный статический метод. Кроме того, в области действия включающего его класса метод расширения может быть вызван с использованием синтаксиса вызова метода экземпляра (§7.5.5.2). В этом случае в качестве первого аргумента используется выражение-получатель.

В следующей программе используются объявленные выше методы расширения:

static class Program
{
static void Main() {
string[] strings = { "1", "22", "333", "4444" };
foreach (string s in strings.Slice(1, 2)) {
Console.WriteLine(s.ToInt32());
}
}
}

Метод Slice доступен для string[], а метод ToInt32 — для string, поскольку оба они объявлены как методы расширения. Эта программа аналогична следующей, в которой используются вызовы обычных статических методов:

static class Program
{
static void Main() {
string[] strings = { "1", "22", "333", "4444" };
foreach (string s in Extensions.Slice(strings, 1, 2)) {
Console.WriteLine(Extensions.ToInt32(s));
}
}
}

10.6.10 Тело метода

Тело_метода объявления содержит блок или точку с запятой.

Объявления абстрактных и внешних методов не предоставляют реализацию метода, поэтому их тела содержат только точку с запятой. Тело любого другого метода содержит блок (§8.2), включающий в себя операторы, которые выполняются при вызове метода.

В теле метода, который имеет тип возвращаемого значения void, не допускается использование операторов return (§8.9.4) для задания выражений. Если выполнение метода, возвращающего void, завершается нормально (управление передается из конечной точки тела метода), этот метод просто возвращается вызвавшему его объекту.

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

В примере

class A
{
public int F() {} // Ошибка. Требуется возвращаемое значение

public int G() {
return 1;
}

public int H(bool b) {
if (b) {
return 1;
}
else {
return 0;
}
}
}

метод F, возвращающий значение, порождает ошибку времени компиляции, поскольку в этом случае управление может быть передано из конечной точки тела метода. Методы G и H являются правильными, поскольку в них все возможные ветви выполнения заканчиваются оператором return, задающим возвращаемое значение.

10.6.11 Перегрузка метода

Правила разрешения перегрузки метода описываются в разделе §7.4.2.

10.7 Свойства

Свойство — это член, предоставляющий доступ к характеристикам объекта или класса. Примеры свойств: размер шрифта, заголовок окна, имя клиента и т. д. Свойства — это естественное расширение полей. Как свойства, так и поля являются именованными членами со связанными типами, для обращения к которым используется одинаковый синтаксис. Однако в отличие от полей, свойства не указывают места хранения. Вместо этого свойства содержат методы доступа, определяющие операторы, которые используются при чтении или записи их значений. Таким образом, свойства предоставляют механизмы, позволяющие связать определенные действия с чтением или записью атрибутов объекта. Кроме того, свойства обеспечивают вычисление таких атрибутов.

Свойства объявляются с помощью объявлений_свойств:

объявление_свойства:
атрибутынеобязательно модификаторы_свойствнеобязательно тип имя_члена { объявления_методов_доступа }

модификаторы_свойств:
модификатор_свойства
модификаторы_свойств модификатор_свойства

модификатор_свойства:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

имя_члена:
идентификатор
тип_интерфейса . идентификатор

Объявление_свойства может включать набор атрибутов (§17), допустимое сочетание любых из четырех модификаторов доступа (§10.3.5), а также модификаторы new (§10.3.4), static (§10.6.2), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6) и extern (§10.6.7).

В отношении использования сочетаний модификаторов объявления свойств подчиняются тем же правилам, что и объявления методов (§10.6).

Тип свойства задает представленный в объявлении тип свойства. Имя_члена задает имя свойства. Если в свойстве не содержится явная реализация члена интерфейса, имя_члена представляет собой просто идентификатор. При явной реализации члена интерфейса (§13.4.1) имя_члена состоит из типа_интерфейса, точки «.» и идентификатора (в указанной последовательности).

Тип свойства должен обладать не меньшим уровнем доступа, чем само свойство (§3.5.4).

Объявления_методов_доступа заключаются в фигурные скобки «{» и «}» и объявляют методы доступа (§10.7.2) свойства. Методы доступа задают исполняемые операторы, связанные с чтением или записью свойства.

Несмотря на то что для обращения к свойствам и полям используется одинаковый синтаксис, свойство не классифицируется как переменная. Таким образом, свойство невозможно передать в качестве аргумента ref или out.

Если объявление свойства содержит модификатор extern, свойство является внешним свойством. Поскольку объявление внешнего свойства не предоставляет фактической реализации, каждое из его объявлений_методов_доступа состоит из точки с запятой.

10.7.1 Статические свойства и свойства экземпляра

Если объявление свойства содержит модификатор static, свойство является статическим свойством. Если модификатор static отсутствует, свойство является свойством экземпляра.

Статическое свойство не связано с конкретным экземпляром. При использовании зарезервированного слова this в методах доступа статического свойства возникает ошибка времени компиляции.

Свойство экземпляра связано с заданным экземпляром класса, обращение к которому может выполняться с помощью зарезервированного слова this (§7.5.7) в методах доступа свойства.

При доступе_к_члену (§7.5.4) с использованием ссылки на свойство в форме E.M для статического свойства M параметр E должен означать тип, содержащий M, а для свойства экземпляра M — экземпляр типа, содержащего M.

Различия между статическими членами и членами экземпляров рассматриваются в разделе §10.3.7.

10.7.2 Методы доступа

Объявления_методов_доступа свойства задают исполняемые операторы, связанные с чтением или записью свойства.

объявления_методов_доступа:
объявление_метода_доступа_get объявление_метода_доступа_setнеобязательно
объявление_метода_доступа_set объявление_метода_доступа_getнеобязательно

объявление_метода_доступа_get:
атрибутынеобязательно модификатор_метода_доступанеобязательно get тело_метода_доступа

объявление_метода_доступа_set:
атрибутынеобязательно модификатор_метода_доступанеобязательно set тело_метода_доступа

модификатор_метода_доступа:
protected
internal
private
protected internal
internal protected

тело_метода_доступа:
блок
;

Объявление метода доступа включает в себя объявление_метода_доступа_get, объявление_метода_доступа_set или оба этих объявления. Каждое объявление метода доступа состоит из маркера get или set, за которым следуют необязательный модификатор_метода_доступа и тело_метода_доступа.

На использование модификаторов_методов_доступа налагаются следующие ограничения.

· Модификатор_метода_доступа не может использоваться в интерфейсе или явной реализации члена интерфейса.

· Для свойства или индексатора, не имеющего модификатора override, модификатор_метода_доступа может использоваться только в том случае, если свойство или индексатор содержит оба метода доступа (get и set), и применяется только к одному из них.

· Для свойства или индексатора, содержащего модификатор override, метод доступа должен соответствовать используемому модификатору_метода_доступа переопределяемого метода доступа.

· Модификатор_метода_доступа должен объявлять более строгий уровень доступа, чем уровень доступа самого свойства или индексатора. Более точно:

o Если для свойства или индексатора объявлен уровень доступа public, можно использовать любой модификатор_метода_доступа.

o Если для свойства или индексатора объявлен уровень доступа protected internal, можно использовать модификатор_метода_доступа с уровнем доступа internal, protected или private.

o Если для свойства или индексатора объявлен уровень доступа internal или protected, можно использовать модификатор_метода_доступа с уровнем доступа private.

o Если для свойства или индексатора объявлен уровень доступа private, использовать модификаторы_метода_доступа нельзя.

Для свойств с модификатором abstract и extern тело_метода_доступа содержит только точку с запятой. Свойства, не являющиеся абстрактными или внешними, могут быть автоматически реализуемыми свойствами. В этом случае задаются оба метода доступа (get и set), в теле которых содержится только точка с запятой (§10.7.3). Тело_метода_доступа для любого свойства, не являющегося абстрактным или внешним, представляет собой блок, в котором задаются операторы, выполняемые при вызове соответствующего метода доступа.

Метод доступа get соответствует не содержащему параметров методу, возвращаемое значение которого имеет тип свойства. За исключением случаев, когда свойство является конечным объектом операции присваивания, при ссылке на свойство в выражении вызывается метод доступа get для вычисления значения свойства (§7.1.1). Тело метода доступа get должно соответствовать правилам для возвращающих значение методов, описанным в §10.6.10. В частности, все операторы return в теле метода доступа get должны задавать выражение, которое может быть неявно преобразовано к типу свойства. Кроме того, конечная точка метода доступа get должна быть недостижима.

Метод доступа set соответствует методу с типом возвращаемого значения void и одним параметром значения, имеющим тип свойства. Неявному параметру метода доступа set всегда присваивается имя value. Если свойство является конечным объектом операции присваивания (§7.16) или операндом операторов «++» или «--» (§7.5.9, §7.6.5), метод доступа set вызывается с аргументом (значение аргумента располагается в правой части операции присваивания или является операндом операторов «++» или «--»), который предоставляет новое значение (§7.16.1). Тело метода доступа set должно соответствовать правилам для возвращающих void методов, описанным в §10.6.10. В частности, операторы return в теле метода доступа set не могут задавать выражение. Поскольку метод доступа set неявно содержит параметр value, в случае объявления в нем локальной переменной или константы с таким же именем возникает ошибка времени компиляции.

В зависимости от наличия или отсутствия методов доступа get и set свойства классифицируются следующим образом.

· Свойство, содержащее оба метода доступа (get и set), называется свойством для чтения и записи.

· Свойство, содержащее только метод доступа get, называется свойством только для чтения. При использовании свойства только для чтения в качестве конечного объекта для операции присваивания возникает ошибка времени компиляции.

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

В примере

public class Button: Control
{
private string caption;

public string Caption {
get {
return caption;
}
set {
if (caption != value) {
caption = value;
Repaint();
}
}
}

public override void Paint(Graphics g, Rectangle r) {
// Место для кода рисования
}
}

элемент управления Button объявляет открытое свойство Caption. Метод доступа get свойства Caption возвращает строку, хранящуюся в закрытом поле caption. Метод доступа set сравнивает новое значение с текущим, и, если оно отличается, сохраняет новое значение и обновляет элемент управления. Для свойств часто используется описанный выше шаблон. Метод доступа get просто возвращает значение, хранящееся в закрытом поле, а метод set изменяет закрытое поле и выполняет определенные действия, необходимые для полного обновления состояния объекта.

В следующем примере показано использование свойства Caption для описанного выше класса Button:

Button okButton = new Button();
okButton.Caption = "ОК"; // Вызывается метод доступа set
string s = okButton.Caption; // Вызывается метод доступа get

В этом примере метод доступа set вызывается посредством присваивания значения свойству, а метод доступа get — посредством ссылки на свойство в выражении.

Методы доступа get и set свойства не являются отдельными членами и не могут объявляться отдельно от свойства. Два метода доступа свойства для чтения и записи не могут иметь различные уровни доступа. В примере

class A
{
private string name;

public string Name { // Ошибка. Дублирующееся имя члена
get { return name; }
}

public string Name { // Ошибка. Дублирующееся имя члена
set { name = value; }
}
}

не объявляется одно свойство для чтения и записи. Вместо этого в нем объявляется два одноименных свойства, одно из которых является свойством только для чтения, а другое — свойством только для записи. Поскольку два члена, объявленные в одном классе, не могут иметь одинаковые имена, возникает ошибка времени компиляции.

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

class A
{
public int P {
set {...}
}
}

class B: A
{
new public int P {
get {...}
}
}

свойство P класса B скрывает свойство P класса A по отношению к операциям чтения и записи. Таким образом, в операторах

B b = new B();
b.P = 1; // Ошибка. Свойство B.P предназначено только для чтения
((A)b).P = 1; // Допустимо. Ссылка на свойство A.P

присваивание свойству b.P вызывает ошибку времени компиляции, поскольку свойство только для чтения P класса B скрывает свойство только для записи P класса A. Обратите внимание, что для доступа к скрытому свойству P можно использовать приведение.

В отличие от открытых полей свойства обеспечивают разделение между внутренним состоянием объекта и его открытым интерфейсом. Рассмотрим пример:

class Label
{
private int x, y;
private string caption;

public Label(int x, int y, string caption) {
this.x = x;
this.y = y;
this.caption = caption;
}

public int X {
get { return x; }
}

public int Y {
get { return y; }
}

public Point Location {
get { return new Point(x, y); }
}

public string Caption {
get { return caption; }
}
}

В этом примере в классе Label используется два поля типа int (x и y) для хранения местоположения надписи. Сведения о местоположении предоставляются открытым образом в виде свойств X и Y, а также свойства Location типа Point. Если в последующих версиях класса Label потребуется внутреннее хранение данных о местоположении с помощью типа Point, это можно реализовать, не внося изменения в открытый интерфейс класса:

class Label
{
private Point location;
private string caption;

public Label(int x, int y, string caption) {
this.location = new Point(x, y);
this.caption = caption;
}

public int X {
get { return location.x; }
}

public int Y {
get { return location.y; }
}

public Point Location {
get { return location; }
}

public string Caption {
get { return caption; }
}
}

Если бы свойства x и y были полями public только для чтения, внести такие изменения в класс Label было бы невозможно.

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

Поскольку вызов метода доступа get концептуально равнозначен чтению значения поля, не рекомендуется, чтобы при использовании методов доступа get выполнялись видимые побочные действия. В примере

class Counter
{
private int next;

public int Next {
get { return next++; }
}
}

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

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

Свойства можно использовать для задержки инициализации ресурса до того момента времени, когда на него будет сделана первая ссылка. Например:

using System.IO;

public class Console
{
private static TextReader reader;
private static TextWriter writer;
private static TextWriter error;

public static TextReader In {
get {
if (reader == null) {
reader = new StreamReader(Console.OpenStandardInput());
}
return reader;
}
}

public static TextWriter Out {
get {
if (writer == null) {
writer = new StreamWriter(Console.OpenStandardOutput());
}
return writer;
}
}

public static TextWriter Error {
get {
if (error == null) {
error = new StreamWriter(Console.OpenStandardError());
}
return error;
}
}
}

Класс Console содержит три свойства (In, Out и Error), которые представляют стандартные устройства ввода, вывода и вывода ошибок соответственно. Благодаря предоставлению этих членов в виде свойств класса Console обеспечивается задержка их инициализации до момента фактического использования. Например, при первой ссылке на свойство Out, как показано в примере

Console.Out.WriteLine("Привет!");

создается базовый объект TextWriter для устройства вывода. Однако если приложение не ссылается на свойства In и Error, объекты для этих устройств не создаются.

10.7.3 Автоматически реализуемые свойства

Для свойства, определенного как автоматически реализуемое свойство, автоматически создается скрытое резервное поле, для которого реализуются методы доступа для чтения и записи.

Следующий пример:

public class Point {
public int X { get; set; } // Реализуется автоматически
public int Y { get; set; } // Реализуется автоматически
}

равнозначен следующему объявлению:

public class Point {
private int x;
private int y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}

Поскольку резервное поле недоступно, чтение и запись его значений осуществляются только с помощью методов доступа свойства. Это означает, что автоматически реализуемые свойства только для чтения или только для записи не имеют смысла и не разрешены. Однако возможно задать различные уровни доступа для каждого из методов доступа. Таким образом, можно имитировать свойство только для чтения с закрытым резервным полем следующим образом:

public class ReadOnlyPoint {
public int X { get; private set; }
public int Y { get; private set; }
public ReadOnlyPoint(int x, int y) { X = x; Y = y; }
}

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

10.7.4 Доступность

Если метод доступа содержит модификатор_метода_доступа, область доступности (§3.5.2) метода определяется на основании объявленной доступности модификатора_метода_доступа. Если метод доступа не содержит модификатор_метода_доступа, область доступности метода определяется на основании объявленной доступности свойства или индексатора.

Наличие модификатора_метода_доступа никогда не влияет на поиск членов (§7.3) или разрешение перегрузки (§7.4.3). Модификаторы свойства или индексатора всегда определяют связанные свойство или индексатор независимо от контекста доступа.

После выбора конкретного свойства или индексатора допустимость их использования определяется на основании областей доступности задействованных методов доступа.

· Для использования в качестве значения (§7.1.1) должен существовать и быть доступным метод доступа get.

· Для использования в качестве конечного объекта операции простого присваивания (§7.16.1) должен существовать и быть доступным метод доступа set.

· Для использования в качестве конечного объекта операции составного присваивания (§7.16.2) или в качестве конечного объекта операторов «++» или «--» (§7.5.9, §7.6.5) должны существовать и быть доступны оба метода доступа (get и set).

В следующем примере свойство A.Text скрывается свойством B.Text даже в тех контекстах, в которых вызывается только метод доступа set. Напротив, свойство B.Count недоступно для класса M, в связи с чем вместо него используется доступное свойство A.Count.

class A
{
public string Text {
get { return "Привет!"; }
set { }
}

public int Count {
get { return 5; }
set { }
}
}

class B: A
{
private string text = "Пока!";
private int count = 0;

new public string Text {
get { return text; }
protected set { text = value; }
}

new protected int Count {
get { return count; }
set { count = value; }
}
}

class M
{
static void Main() {
B b = new B();
b.Count = 12; // Вызывается метод доступа set свойства A.Count
int i = b.Count; // Вызывается метод доступа get свойства A.Count
b.Text = "Как дела?"; // Ошибка. Метод доступа set свойства B.Text недоступен
string s = b.Text; // Вызывается метод доступа get свойства B.Text
}
}

Метод доступа, используемый для реализации интерфейса, может не содержать модификатор_метод_доступа. Если для реализации интерфейса используется только один метод доступа, второй метод доступа может быть объявлен с помощью модификатора_метода_доступа:

public interface I
{
string Prop { get; }
}

public class C: I
{
public Prop {
get { return "Апрель"; } // Использование модификатора не допускается
internal set {...} // Допустимо, поскольку свойство I.Prop не содержит метод доступа set
}
}

10.7.5 Виртуальные, запечатанные, переопределяющие и абстрактные методы доступа

Объявление свойства virtual указывает, что методы доступа свойства являются виртуальными. Модификатор virtual применяется к обоим методам доступа свойства для чтения и записи. Объявление в качестве виртуального только одного метода доступа такого свойства не допускается.

Объявление свойства abstract указывает, что методы доступа свойства являются виртуальными, однако не предоставляет их фактической реализации. Вместо этого необходимо определить неабстрактные производные классы, предоставляющие собственные реализации методов доступа посредством переопределения свойства. Поскольку метод доступа для объявления абстрактного свойства не предоставляет фактической реализации, тело_метода_доступа содержит только точку с запятой.

Объявление свойства, содержащее одновременно модификаторы abstract и override, указывает, что свойство является абстрактным и переопределяет базовое свойство. Методы доступа такого свойства также являются абстрактными.

Объявления абстрактных свойств допускаются только в абстрактных классах (§10.1.1.1). Методы доступа унаследованного виртуального свойства можно переопределить в производном классе, включив объявление свойства, задающее директиву override. Этот способ называется переопределяющим объявлением свойства. Переопределяющее объявление свойства не объявляет новое свойство. Вместо этого оно уточняет реализацию методов доступа существующего виртуального свойства.

Переопределяющее объявление свойства должно задавать в точности такие же модификаторы доступа, тип и имя, что и у унаследованного свойства. Если унаследованное свойство содержит только один метод доступа (то есть является свойством только для чтения или только для записи), переопределяющее свойство должно включать точно такой же метод доступа. Если унаследованное свойство содержит оба метода доступа (является свойством для чтения и записи), переопределяющее свойство может включать один или оба метода доступа.

Объявление переопределяющего свойства может содержать модификатор sealed. Использование этого модификатора позволяет предотвратить последующее переопределение свойства в производных классах. Методы доступа запечатанного свойства также являются запечатанными.

За исключением различий в синтаксисе объявления и вызова, поведение виртуальных, запечатанных, переопределяющих или абстрактных методов доступа в точности соответствует поведению виртуальных, запечатанных, переопределяющих или абстрактных методов. В частности, правила, описанные в разделах §10.6.3, §10.6.4, §10.6.5 и §10.6.6, применяются к методам доступа как к методам соответствующего вида.

· Метод доступа get соответствует не имеющему параметров методу, имеющему возвращаемое значение типа свойства и те же модификаторы, что и содержащее свойство.

· Метод доступа set соответствует методу с типом возвращаемого значения void, одним параметром значения, имеющим тип свойства, и имеющему те же модификаторы, что и содержащее свойство.

В примере

abstract class A
{
int y;

public virtual int X {
get { return 0; }
}

public virtual int Y {
get { return y; }
set { y = value; }
}

public abstract int Z { get; set; }
}

X — это виртуально свойство только для чтения, Y — это виртуальное свойство только для записи, а Z — это абстрактное свойство для чтения и записи. Поскольку свойство Z является абстрактным, содержащий его класс A также должен быть объявлен как абстрактный.

В следующем примере показан класс, производный от класса A:

class B: A
{
int z;

public override int X {
get { return base.X + 1; }
}

public override int Y {
set { base.Y = value < 0? 0: value; }
}

public override int Z {
get { return z; }
set { z = value; }
}
}

В этом примере объявления свойств X, Y и Z представляют собой переопределяющие объявления свойств. Каждое объявление свойства имеет точно такие же модификаторы доступа, тип и имя, что и соответствующее унаследованное свойство. В методе доступа get свойства X и методе доступа set свойства Y используется ключевое слово base для обращения к унаследованным методам доступа. Объявление свойства Z переопределяет оба абстрактных метода доступа. Таким образом, в классе B отсутствуют необработанные члены абстрактной функции, в связи с чем класс B может быть неабстрактным классом.

Если свойство объявлено с помощью модификатора override, все переопределенные методы доступа должны быть доступны переопределяющему коду. Кроме того, объявленные уровни доступа для самого свойства или индексатора и его методов доступа должны соответствовать уровням доступа переопределенного члена и его методов доступа. Например:

public class B
{
public virtual int P {
protected set {...}
get {...}
}
}

public class D: B
{
public override int P {
protected set {...} // Необходимо задать protected
get {...} // Использование модификатора не допускается
}
}

10.8 События

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

События объявляются с помощью объявлений_событий:

объявление_события:
атрибутынеобязательно модификаторы_событийнеобязательно event тип деклараторы переменных ;
атрибутынеобязательно модификаторы_событийнеобязательно event тип имя_члена { объявления_методов_доступа_события }

модификаторы_событий:
модификатор_события
модификаторы_событий модификатор_события

модификатор_события:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

объявления_методов_доступа события:
объявление_метода_доступа_add объявление_метода_доступа_remove
объявление_метода_доступа_remove объявление_метода_доступа_add

объявление_метода_доступа_add:
атрибутынеобязательно add блок

объявление_метода_доступа_remove:
атрибутынеобязательно remove блок

Объявление_события может включать набор атрибутов (§17), допустимое сочетание любых из четырех модификаторов доступа (§10.3.5), а также модификаторы new (§10.3.4), static (§10.6.2), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6) и extern (§10.6.7).

В отношении использования сочетаний модификаторов объявления событий подчиняются тем же правилам, что и объявления методов (§10.6).

Тип объявления события должен быть типом_делегата (§4.2) с уровнем доступности не меньшим, чем у самого события (§3.5.4).

Объявление события может включать объявления_методов_доступа_событий. Однако если такие объявления отсутствуют, для событий, не являющихся абстрактными или внешними, компилятор автоматически предоставляет их (§10.8.1). Для внешних событий методы доступа предоставляются внешне.

Объявление события, в котором опущены объявления_методов_доступа_событий, определяет одно или несколько событий — по одному для каждого декларатора_переменной. Атрибуты и модификаторы применяются ко всем членам, объявленным с помощью такого объявления_события.

Если объявление_события содержит одновременно модификатор abstract и заключенные в фигурные скобки объявления_методов_доступа_событий, возникает ошибка времени компиляции.

Если объявление события содержит модификатор extern, событие является внешним событием. Поскольку объявления внешних событий не предоставляют фактической реализации, при наличии одновременно модификатора extern и объявлений_методов_доступа_событий возникает ошибка.

Событие может использоваться в качестве левого операнда в операторах «+=» и «-=» (§7.16.3). Эти операторы используются для присоединения к событию обработчика событий или его удаления соответственно. Модификаторы доступа события определяют контексты, в которых такие операции разрешены.

Операторы «+=» и «-=» являются единственно допустимыми операторами для события вне типа, в котором оно объявлено. В связи с этим внешний код может добавлять обработчики к событию и удалять их, однако не может получать или изменять базовый список обработчиков событий.

В операциях вида x += y или x -= y (где для события x выполняется ссылка за пределы типа, который содержит объявление x) результат операции имеет тип void (а не значение x с типом x после присваивания). Это правило не допускает непосредственного просмотра базового делегата события внешним кодом.

В следующем примере показан порядок вложения обработчиков событий в экземпляры класса Button:

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control
{
public event EventHandler Click;
}

public class LoginDialog: Form
{
Button OkButton;
Button CancelButton;

public LoginDialog() {
OkButton = new Button(...);
OkButton.Click += new EventHandler(OkButtonClick);
CancelButton = new Button(...);
CancelButton.Click += new EventHandler(CancelButtonClick);
}

void OkButtonClick(object sender, EventArgs e) {
// Обрабатывает событие OkButton.Click
}

void CancelButtonClick(object sender, EventArgs e) {
// Обрабатывает событие CancelButton.Click
}
}

В этом примере с помощью конструктора экземпляра LoginDialog создается два экземпляра класса Button и присоединяются обработчики событий для событий Click.

10.8.1 События, подобные полям

В тексте программы в классе или структуре, содержащих объявление события, некоторые события можно использовать как поля. Для такого использования событие не должно быть abstract или extern и не должно явно включать объявления_методов_доступа_к_событиям. Такое событие можно использовать в любом контексте, где разрешено использование поля. Это поле содержит делегат (§15), ссылающийся на список обработчиков событий, добавленных к этому событию. Если обработчики событий не добавлены, поле содержит null.

В примере

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control
{
public event EventHandler Click;

protected void OnClick(EventArgs e) {
if (Click != null) Click(this, e);
}

public void Reset() {
Click = null;
}
}

Событие Click используется как поле внутри класса Button. Как показано в примере, это поле можно проверять, изменять и использовать в выражениях вызова делегата. Метод OnClick в классе Button "вызывает" событие Click. Понятие вызова события совершенно эквивалентно вызову делегата, представленного событием—так, не существует специальных языковых конструкций для вызова событий. Обратите внимание, что вызову делегата предшествует проверка, что делегат не равен null.

Вне объявления класса Button член Click может использоваться только с левой стороны операторов += и –=, как в

b.Click += new EventHandler(…);

где делегат добавляется к списку вызовов события Click, и в

b.Click –= new EventHandler(…);

где делегат удаляется из списка вызовов события Click.

При компиляции события, подобного полю, компилятор автоматически создает хранилище для хранения делегата и создает методы доступа для события, добавляющие или удаляющие обработчики событий поля делегата. Чтобы быть потокобезопасными, операции добавления и удаления выполняются при блокировке (§8.12) содержащего объекта для события экземпляра или объекта типа (§7.5.10.6) для статического события.

Так, объявление события экземпляра вида:

class X
{
public event D Ev;
}

можно скомпилировать в нечто, эквивалентное:

class X
{
private D __Ev; // поле для хранения делегата

public event D Ev {
add {
lock(this) { __Ev = __Ev + value; }
}

remove {
lock(this) { __Ev = __Ev - value; }
}
}
}

Внутри класса X ссылки на Ev компилируются в ссылки на скрытое поле __Ev. Имя "__Ev" произвольное; скрытое поле могло бы иметь любое имя или вообще его не иметь.

Аналогично объявление статического события вида:

class X
{
public static event D Ev;
}

можно скомпилировать в нечто, эквивалентное:

class X
{
private static D __Ev; // поле для хранения делегата

public static event D Ev {
add {
lock(typeof(X)) { __Ev = __Ev + value; }
}

remove {
lock(typeof(X)) { __Ev = __Ev - value; }
}
}
}

10.8.2 Методы доступа к событиям

В объявлениях событий обычно опускаются объявления_доступа_к_событиям, как в вышеприведенном примере Button. В число причин для этого входит случай, когда затраты на хранение одного поля на каждое событие не являются приемлемыми. В таких случаях класс может включать объявления_методов_доступа_к_событиям и использовать частный механизм для хранения списка обработчиков событий.

Объявления_методов_доступа_к_событиям события указывают исполняемые операторы, связанные с добавлением и удалением обработчиков событий.

Объявления методов доступа состоят из объявления_метода_доступа_add и объявления_метода_доступа_remove. Каждое объявление метода доступа состоит из лексемы add или remove, за которой следует блок. В блоке, связанном с объявлением_метода_доступа_add, указываются операторы, выполняемые при добавлении обработчика событий, а в блоке, связанном с объявлением_метода_доступа_remove, указываются операторы, выполняемые при удалении обработчика событий.

Каждое объявление_метода_доступа_add и объявление_метода_доступа_remove соответствует методу с одним параметром значения типа события и типом возвращаемого значения void. Неявный параметр метода доступа к событию называется value. Когда событие используется в назначении события, используется соответствующий метод доступа к событию. В частности, если оператором присваивания является +=, используется метод доступа add, а если оператор присваивания -=, используется метод доступа remove. В любом случае правый операнд оператора присваивания используется в качестве аргумента метода доступа к событию. Блок объявления_метода_доступа_add или объявления_метода_доступа_remove должен соответствовать правилам для методов void, описанным в §10.6.10. В частности, операторам return в таком блоке запрещено указывать выражение.

Поскольку в методе доступа к событию неявно имеется параметр с именем value, объявление в методе доступа к событию локальной переменной или константы с таким именем является ошибкой времени компиляции.

В примере

class Control: Component
{
// Уникальные ключи для событий
static readonly object mouseDownEventKey = new object();
static readonly object mouseUpEventKey = new object();

// Вернуть обработчик событий, связанный с ключом
protected Delegate GetEventHandler(object key) {...}

// Добавить обработчик событий, связанный с ключом
protected void AddEventHandler(object key, Delegate handler) {...}

// Удалить обработчик событий, связанный с ключом
protected void RemoveEventHandler(object key, Delegate handler) {...}

// Событие MouseDown
public event MouseEventHandler MouseDown {
add { AddEventHandler(mouseDownEventKey, value); }
remove { RemoveEventHandler(mouseDownEventKey, value); }
}

// Событие MouseUp
public event MouseEventHandler MouseUp {
add { AddEventHandler(mouseUpEventKey, value); }
remove { RemoveEventHandler(mouseUpEventKey, value); }
}

// Вызвать событие MouseUp
protected void OnMouseUp(MouseEventArgs args) {
MouseEventHandler handler;
handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
if (handler != null)
handler(this, args);
}
}

класс Control реализует механизм внутреннего хранилища для событий. Метод AddEventHandler связывает значение делегата с ключом, метод GetEventHandler возвращает делегат, в данный момент связанный с ключом, а метод RemoveEventHandler удаляет делегат в качестве обработчика событий для указанного события. Предположительно, лежащий в основе механизм хранилища разработан так, что отсутствуют затраты на связывание значения делегата null с ключом, и таким образом необрабатываемые события не расходуют емкость хранилища.

10.8.3 Статические события и события экземпляров

Если в объявление события включен модификатор static, событие называют статическим событием. Если нет модификатора static, событие называют событием экземпляра.

Статическое событие не связано с конкретным экземпляром, и обращение к this в методах доступа статического события является ошибкой времени компиляции.

Событие экземпляра связано с данным экземпляром класса, и к этому экземпляру можно обращаться как к this (§7.5.7) в методах доступа этого события.

Когда к событию обращаются через доступ_к_члену (§7.5.4) вида E.M, если M является статическим событием, E должно означать тип, содержащий M, а если M является событием экземпляра, E должно означать экземпляр типа, содержащего M.

Различия между статическими членами и членами экземпляров обсуждаются далее в §10.3.7.

10.8.4 Виртуальные, запечатанные, переопределяющие и абстрактные методы доступа

Объявление события как virtual указывает, что методы доступа этого события являются виртуальными. Модификатор virtual применяется к обоим методам доступа события.

Объявление события как abstract указывает, что методы доступа события являются виртуальными, но не предоставляет фактическую реализацию методов доступа. Вместо этого требуются не абстрактные производные классы для предоставления их собственной реализации для методов доступа посредством переопределения события. Поскольку объявление абстрактного события не предоставляет фактическую реализацию, оно не может предоставить разделенные скобками объявления_методов_доступа_к_событиям.

Объявление события, включающее оба модификатора abstract и override, указывает, что событие является абстрактным и переопределяет основное событие. Методы доступа такого события также являются абстрактными.

Объявления абстрактных событий разрешены только в абстрактных классах (§10.1.1.1).

Методы доступа унаследованного виртуального события могут быть переопределены в производном классе включением объявления события, указывающего модификатор override. Это называется объявлением переопределяющего события. Объявление переопределяющего события не объявляет новое событие. Вместо этого оно просто специализирует реализации методов доступа существующего виртуального события.

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

Объявление переопределяющего события может включать модификатор sealed. Использование этого модификатора предотвращает дальнейшее переопределение события производным классом. Методы доступа запечатанного события также запечатаны.

Включение модификатора new в объявление переопределяющего события является ошибкой времени компиляции.

За исключением различий в синтаксисе объявления и вызова, поведение виртуальных, запечатанных, переопределяющих и абстрактных методов доступа точно такое же, как поведение виртуальных, запечатанных, переопределяющих и абстрактных методов. В частности, правила, описанные в §10.6.3, §10.6.4, §10.6.5 и §10.6.6, применяются, как если бы методы доступа были методами соответствующего вида. Каждый метод доступа соответствует методу с единственным параметром-значением типа события, с типом возвращаемого значения void и с теми же модификаторами, что содержащее событие.

10.9 Индексаторы

Индексатор является членом, дающим возможность индексировать объект так же, как массив. Индексаторы объявляются с помощью объявлений_индексаторов:

объявление_индексатора:
атрибутынеоб модификаторы_индексаторанеоб декларатор_индексатора { объявления_методов_доступа }

модификаторы_индексатора:
модификатор_индексатора
модификаторы_индексатора модификатор_индексатора

модификатор_индексатора:
new
public
protected
internal
private
virtual
sealed
override
abstract
extern

декларатор_индексатора:
тип this [ список_формальных_параметров ]
тип тип_интерфейса . this [ список-формальных-параметров ]

Объявление_индексатора может включать набор атрибутов (§17) и допустимое сочетание из четырех модификаторов доступа (§10.3.5) и модификаторы new (§10.3.4), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6) и extern (§10.6.7).

Объявления индексаторов подчиняются тем же правилам, что и объявления методов (§10.6), в отношении допустимых сочетаний модификаторов, с одним исключением – модификатор static не допускается в объявлении индексатора.

Модификаторы virtual, override и abstract являются взаимоисключающими, кроме как в одном случае. Модификаторы abstract и override могут использоваться совместно, так чтобы абстрактный индексатор мог переопределить виртуальный.

Тип в объявлении индексатора указывает тип элемента индексатора, вводимого объявлением. Если только индексатор не является явной реализацией члена интерфейса, за типом следует зарезервированное слово this. Для явной реализации члена интерфейса за типом следует тип_интерфейса, «.» и зарезервированное слово this. В отличие от других членов, индексаторы не имеют имен, определяемых пользователем.

Список_формальных_параметров указывает параметры индексатора. Список формальных параметров индексатора соответствует списку формальных параметров метода (§10.6.1), кроме того, что по крайней мере один параметр должен быть указан, а модификаторы параметров ref и out не разрешены.

Тип индексатора и каждый из типов, на которые есть ссылки в списке_формальных_параметров должны быть по крайней мере так же доступными, как сам индексатор (§3.5.4).

Объявления_методов_доступа (§10.7.2), которые должны быть заключены в лексемы «{" и "}», объявляют методы доступа индексатора. Методы доступа указывают исполняемые операторы, связанные с чтением и записью элементов индексатора.

Хотя синтаксис доступа к элементу индексатора такой же, как синтаксис доступа к элементу массива, элемент индексатора не классифицируется как переменная. Так, невозможно передать элемент индексатора в качестве аргумента ref или out.

Список формальных параметров индексатора определяет подпись (§3.6) индексатора. В частности, подпись индексатора состоит из числа и типов его формальных параметров. Тип элемента и имена формальных параметров не являются частью подписи индексатора.

Подпись индексатора должна отличаться от подписей всех других индексаторов, объявленных в этом же классе.

Понятия индексаторов и свойств очень схожи, но отличаются следующим:

· свойство идентифицируется своим именем, а индексатор идентифицируется своей подписью;

· доступ к свойству производится через простое_имя (§7.5.2) или доступ_к_члену (§7.5.4), а доступ к элементу индексатора производится через доступ_к_элементу (§7.5.6.2);

· свойство может быть static членом, а индексатор всегда является членом экземпляра;

· метод доступа get свойства соответствует методу без параметров, тогда как метод доступа get индексатора соответствует методу с тем же списком формальных параметров, что у индексатора;

· метод доступа set свойства соответствует методу с одним параметром с именем value, тогда как метод доступа set индексатора соответствует методу с тем же списком формальных параметров, что у индексатора, плюс дополнительный параметр с именем value;

· объявление в методе доступа к индексатору локальной переменной с тем же именем, что параметр индексатора, является ошибкой времени компиляции;

· в объявлении переопределяющего свойства при обращении к унаследованному свойству используется синтаксис base.P, где P – имя свойства. В объявлении переопределяющего индексатора при обращении к унаследованному индексатору используется синтаксис base[E], где E – список выражений, разделенных запятыми.

За исключением этих различий, все правила, определенные в §10.7.2 и §10.7.3, применяются к методам доступа к индексатору, а также и к методам доступа к свойствам.

Если в объявление индексатора включен модификатор extern, индексатор называется внешним индексатором. Так как объявление внешнего индексатора не предоставляет фактической реализации, каждое из его объявлений_метода_доступа состоит из точки с запятой.

В примере внизу объявлен класс BitArray, реализующий индексатор для доступа к отдельным битам в битовом массиве.

using System;

class BitArray
{
int[] bits;
int length;

public BitArray(int length) {
if (length < 0) throw new ArgumentException();
bits = new int[((length - 1) >> 5) + 1];
this.length = length;
}

public int Length {
get { return length; }
}

public bool this[int index] {
get {
if (index < 0 || index >= length) {
throw new IndexOutOfRangeException();
}
return (bits[index >> 5] & 1 << index) != 0;
}
set {
if (index < 0 || index >= length) {
throw new IndexOutOfRangeException();
}
if (value) {
bits[index >> 5] |= 1 << index;
}
else {
bits[index >> 5] &= ~(1 << index);
}
}
}
}

Экземпляр класса BitArray расходует существенно меньше памяти, чем соответствующий bool[] (так как каждое значение первого занимает только один бит, тогда как последнего – один байт), но позволяет выполнять те же операции, что bool[].

В следующем классе CountPrimes используется BitArray и классический алгоритм «решето» для вычисления количества простых чисел между 1 и заданным максимумом:

class CountPrimes
{
static int Count(int max) {
BitArray flags = new BitArray(max + 1);
int count = 1;
for (int i = 2; i <= max; i++) {
if (!flags[i]) {
for (int j = i * 2; j <= max; j += i) flags[j] = true;
count++;
}
}
return count;
}

static void Main(string[] args) {
int max = int.Parse(args[0]);
int count = Count(max);
Console.WriteLine("Найдено {0} простых чисел между 1 и {1}", count, max);
}
}

Обратите внимание, что синтаксис обращения к элементам BitArray точно такой же, как для bool[].

В следующем примере показан класс сетки 26 ґ 10 с индексатором с двумя параметрами. Первым параметром должна быть буква на верхнем или нижнем регистре в диапазоне A–Z, а вторым – целое в диапазоне 0–9.

using System;

class Grid
{
const int NumRows = 26;
const int NumCols = 10;

int[,] cells = new int[NumRows, NumCols];

public int this[char c, int col] {
get {
c = Char.ToUpper(c);
if (c < 'A' || c > 'Z') {
throw new ArgumentException();
}
if (col < 0 || col >= NumCols) {
throw new IndexOutOfRangeException();
}
return cells[c - 'A', col];
}

set {
c = Char.ToUpper(c);
if (c < 'A' || c > 'Z') {
throw new ArgumentException();
}
if (col < 0 || col >= NumCols) {
throw new IndexOutOfRangeException();
}
cells[c - 'A', col] = value;
}
}
}

10.9.1 Перегрузка индексатора

Правила разрешения перегрузки индексаторов описаны в §7.4.2.

10.10 Операторы

Оператор является членом, определяющим значение оператора выражения, который можно применить к экземплярам класса. Операторы объявляются с помощью объявлений_операторов:

объявление_оператора:
атрибутынеоб модификаторы_оператора декларатор_оператора тело_оператора

модификаторы_оператора:
модификатор_оператора
модификаторы_оператора модификатор_оператора

модификатор_оператора:
public
static
extern

декларатор_оператора:
декларатор_унарного_оператора
декларатор_бинарного_оператора
декларатор_оператора_преобразования

декларатор_унарного_оператора:
тип operator перегружаемый_унарный_оператор ( тип идентификатор )

перегружаемый_унарный_оператор: одно из
+ - ! ~ ++ -- true false

декларатор_бинарного_оператора:
тип operator перегружаемый_бинарный_оператор ( тип идентификатор , тип идентификатор )

перегружаемый_бинарный_оператор:
+
-
*
/
%
&
|
^
<<
right_shift
==
!=
>
<
>=
<=

декларатор_оператора_преобразования:
implicit operator тип ( тип идентификатор )
explicit operator тип ( тип идентификатор )

тело_оператора:
блок
;

Есть три категории перегружаемых операторов: унарные операторы (§10.10.1), бинарные операторы (§10.10.2) и операторы преобразования (§10.10.3).

Если в объявление оператора включен модификатор extern, оператор называется внешним оператором. Так как внешний оператор не предоставляет фактическую реализацию, его тело_оператора состоит из точки с запятой. Для всех других операторов тело_оператора состоит из блока, в котором указаны операторы, выполняемые при вызове этого оператора. Блок оператора должен удовлетворять правилам для методов, возвращающих значение, описанным в §10.6.10.

Следующие правила применяются ко всем объявлениям операторов:

· объявление оператора должно включать оба модификатора: public и static;

· параметрами оператора должны быть параметры-значения. Указание в объявлении оператора параметров ref или out является ошибкой времени компиляции;

· подпись оператора (§10.10.1, §10.10.2, §10.10.3) должна отличаться от подписей всех других операторов, объявленных в этом же классе;

· все типы, на которые ссылается объявление оператора, должны быть по крайней мере так же доступными, как сам оператор (§3.5.4);

· неоднократное появление одного и того же модификатора в объявлении оператора является ошибкой времени компиляции.

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

Подобно другим членам, операторы, объявленные в базовом классе, наследуются производными классами. Так как объявления операторов всегда требуют участия класса или структуры, где объявлен оператор, в подписи оператора, то для оператора, объявленного в производном классе, невозможно скрыть оператор, объявленный в базовом классе. Таким образом, модификатор new никогда не требуется и поэтому никогда не допускается в объявлении оператора.

Дополнительные сведения об унарных и бинарных операторах можно найти в §7.2.

Дополнительные сведения об операторах преобразования можно найти в §6.4.

10.10.1 Унарные операторы

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

· унарный оператор +, -, ! или ~ должен принимать единственный параметр типа T или T? и может возвращать любой тип;

· унарный оператор ++ или – должен принимать единственный параметр типа T или T? и должен возвращать тот же самый тип или тип, производный от него;

· унарный оператор true или false должен принимать единственный параметр типа T или T? и должен возвращать тип bool.

Подпись унарного оператора состоит из лексемы оператора (+, -, !, ~, ++, --, true или false) и типа единственного формального параметра. Тип возвращаемого значения не является частью подписи унарного оператора, как и имя формального параметра.

Для унарных операторов true и false требуется попарное объявление. Если в классе объявлен один из этих операторов без объявления другого, возникает ошибка времени компиляции. Операторы true и false описаны далее в §7.11.2 и §7.19.

В следующем примере показана реализация и последующее применение operator ++ для класса целочисленного вектора:

public class IntVector
{
public IntVector(int length) {...}

public int Length {...} // свойство только для чтения

public int this[int index] {...} // индексатор чтения-записи

public static IntVector operator ++(IntVector iv) {
IntVector temp = new IntVector(iv.Length);
for (int i = 0; i < iv.Length; i++)
temp[i] = iv[i] + 1;
return temp;
}
}

class Test
{
static void Main() {
IntVector iv1 = new IntVector(4); // вектор 4 x 0
IntVector iv2;

iv2 = iv1++; // iv2 содержит 4 x 0, iv1 содержит 4 x 1
iv2 = ++iv1; // iv2 содержит 4 x 2, iv1 содержит 4 x 2
}
}

Обратите внимание, как метод оператора возвращает значение, созданное добавлением 1 к операнду, точно так же, как операторы постфиксного увеличения и уменьшения (§7.6.5) и операторы префиксного увеличения и уменьшения (§7.5.9). В отличие от C++, этому методу не требуется непосредственно модифицировать значение своего операнда. Фактически модификация значения операнда нарушала бы стандартную семантику оператора постфиксного увеличения.

10.10.2 Бинарные операторы

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

· бинарный оператор не-сдвига должен принимать два параметра, по крайней мере один из которых должен иметь тип T или T?, и может возвращать любой тип;

· бинарный оператор << или >> должен принимать два параметра, первый из которых должен иметь тип T или T?, а второй должен иметь тип int или int?, и может возвращать любой тип.

Подпись бинарного оператора состоит из лексемы оператора (+, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >= или <=) и типов этих двух формальных параметров. Тип возвращаемого значения и имена формальных параметров не являются частью подписи бинарного оператора.

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

· operator == и operator !=

· operator > и operator <

· operator >= и operator <=

10.10.3 Операторы преобразования

Объявление оператора преобразования вводит преобразование, определенное пользователем(§6.4), которое дополняет предопределенные неявные и явные преобразования.

Объявление оператора преобразования, включающее зарезервированное слово implicit, вводит неявное преобразование, определенное пользователем. Неявные преобразования могут происходить в разнообразных ситуациях, включая вызовы членов функций, приведение выражений и присваивания. Это описано далее в §6.1.

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

Оператор преобразования преобразует от исходного типа, указанного типом параметра оператора преобразования, к конечному типу, указанному типом возврата оператора преобразования.

Для данного исходного типа S и конечного типа T, если S или T являются типами, допускающими присваивание пустой ссылки, пусть S0 и T0 ссылаются на свои основные типы, иначе S0 и T0 равны S и T соответственно. В классе или структуре разрешено объявлять преобразование от исходного типа S к конечному типу T, только если справедливо все следующее:

· S0 и T0 являются разными типами;

· либо S0, либо T0 является типом структуры или класса, где имеет место объявление этого оператора;

· ни S0, ни T0 не является типом_интерфейса;

· без преобразований, определенных пользователем, не существует преобразование от S к T или от T к S.

 

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

В примере

class C<T> {...}

class D<T>: C<T>
{
public static implicit operator C<int>(D<T> value) {...} // ОК

public static implicit operator C<string>(D<T> value) {...} // ОК

public static implicit operator C<T>(D<T> value) {...} // Ошибка
}

первые два объявления операторов разрешены, так как, по замыслу §10.9.3, T и int и string соответственно считаются уникальными типами без отношений. Однако третий оператор ошибочен, так как C<T> является базовым классом для D<T>.

Из второго правила следует, что оператор преобразования должен преобразовывать либо к, либо от типа класса или структуры, где оператор объявлен. Например, можно для типа класса или структуры C определить преобразование от C к int и от int к C, но не от int к bool.

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

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

struct Convertible<T>
{
public static implicit operator Convertible<T>(T value) {...}

public static explicit operator T(Convertible<T> value) {...}
}

если тип object указан в качестве аргумента типа для T, второй оператор объявляет преобразование, которое уже существует (неявное и, следовательно, также и явное преобразование существует от любого типа к типу object).

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

· если существует неявное предопределенное преобразование (§6.1) от типа S к типу T, все определенные пользователем преобразования (неявные или явные) от S к T игнорируются;

· если существует явное предопределенное преобразование (§6.2) от типа S к типу T, любые определенные пользователем явные преобразования от S к T игнорируются. Однако определенные пользователем неявные преобразования от S к T все же признаются.

Для всех типов, кроме object, операторы, объявленные типом Convertible<T> вверху, не противоречат предопределенным преобразованиям. Пример.

void F(int i, Convertible<int> n) {
i = n; // Ошибка
i = (int)n; // Определенное пользователем явное преобразование
n = i; // Определенное пользователем неявное преобразование
n = (Convertible<int>)i; // Определенное пользователем неявное преобразование
}

Однако для типа object предопределенные преобразования скрывают определенные пользователем преобразования во всех случаях, кроме одного:

void F(object o, Convertible<object> n) {
o = n; // Предопределенное преобразование упаковки
o = (object)n; // Предопределенное преобразование упаковки
n = o; // Определенное пользователем неявное преобразование
n = (Convertible<object>)o; // Предопределенное преобразование распаковки
}

Определенным пользователем преобразованиям запрещено преобразование от или к типам_интерфейса. В частности, это ограничение обеспечивает отсутствие каких-либо пользовательских преобразований при преобразовании к типу_интерфейса, а также успешность преобразования к типу_интерфейса в том случае, если преобразуемый объект действительно реализует указанный тип_интерфейса.

Подпись оператора преобразования состоит из исходного типа и конечного типа. (Обратите внимание, что это единственный вид члена, в котором тип возвращаемого значения участвует в подписи.) Классификация implicit или explicit оператора преобразования не является частью подписи оператора. Так, в классе или структуре нельзя объявить операторы преобразования и implicit и explicit с теми же исходными и конечными типами.

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

В примере

using System;

public struct Digit
{
byte value;

public Digit(byte value) {
if (value < 0 || value > 9) throw new ArgumentException();
this.value = value;
}

public static implicit operator byte(Digit d) {
return d.value;
}

public static explicit operator Digit(byte b) {
return new Digit(b);
}
}

преобразование от Digit к byte неявное, так как оно никогда не вызывает исключения и не теряет информацию, а преобразование от byte к Digit явное, так как Digit может представлять только поднабор возможных значений byte.

10.11 Конструкторы экземпляров

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

объявление_конструктора:
атрибутынеоб модификаторы_конструкторанеоб декларатор_конструктора тело_конструктора

модификаторы_конструктора:
модификатор_конструктора
модификаторы_конструктора модификатор_конструктора

модификатор_конструктора:
public
protected
internal
private
extern

декларатор_конструктора:
идентификатор ( список_формальных_параметровнеоб ) инициализатор_конструкторанеоб

инициализатор_конструктора:
: base ( список_аргументовнеоб )
: this ( список_аргументовнеоб )

тело_конструктора:
блок
;

Объявление_конструктора может включать набор атрибутов (§17), допустимое сочетание из четырех модификаторов доступа (§10.3.5) и модификатор extern (§10.6.7). В объявление конструктора запрещено включать один и тот же модификатор несколько раз.

Идентификатор в деклараторе_конструктора должен указывать имя класса, в котором объявлен конструктор экземпляров. Если указано любое другое имя, происходит ошибка времени компиляции.

Необязательный список_формальных_параметров конструктора экземпляров подчиняется тем же правилам, что и список_формальных_параметров метода (§10.6). Список формальных параметров определяет подпись (§3.6) конструктора экземпляров и управляет процессом, посредством которого разрешение перегрузки (§7.4.2) выбирает отдельный конструктор экземпляров в вызове.

Каждый тип, на который есть ссылка в списке_формальных_параметров конструктора экземпляров, должен быть по крайней мере так же доступным, как сам конструктор (§3.5.4).

Необязательный инициализатор_конструктора указывает другой конструктор экземпляров для вызова перед выполнением операторов, заданных в теле_конструктора этого конструктора экземпляров. Это описано далее в §10.11.1.

Если в объявление конструктора включен модификатор extern, конструктор называется внешним конструктором. Поскольку объявление внешнего конструктора не предоставляет фактическую реализацию, его тело_конструктора состоит из точки с запятой. Во всех других конструкторах тело_конструктора состоит из блока, в котором указаны операторы для инициализации нового экземпляра класса. Это в точности соответствует блоку экземпляра метода с типом возвращаемого значения void (§10.6.10).

Конструкторы экземпляров не наследуются. Таким образом, у класса нет конструкторов экземпляров, отличных от тех, что действительно объявлены в этом классе. Если класс не содержит объявления конструкторов экземпляров, автоматически предоставляется конструктор экземпляров по умолчанию (§10.11.4).

Конструкторы экземпляров вызываются выражениями_создания_объектов (§7.5.10.1) и посредством инициализаторов_конструкторов.

10.11.1 Инициализаторы конструкторов

Все конструкторы экземпляров (за исключением конструкторов для класса object) неявно включают вызов другого конструктора экземпляров непосредственно перед телом_конструктора. Конструктор, который должен быть неявно вызван, определяется инициализатором_конструктора:

· инициализатор конструктора экземпляров вида base(список_аргументовнеоб) побуждает вызов конструктора экземпляров из прямого базового класса. Этот конструктор выбирается с помощью списка_аргументов и правил разрешения перегрузки в §7.4.3. Набор конструкторов экземпляров – кандидатов состоит из всех доступных конструкторов экземпляров, содержащихся в прямом базовом классе, или из конструктора по умолчанию (§10.11.4), если в прямом базовом классе не объявлены конструкторы экземпляров. Если этот набор пуст или если не удается идентифицировать один наилучший конструктор экземпляров, возникает ошибка времени компиляции;

· инициализатор конструктора экземпляров вида this(список_аргументовнеоб) побуждает вызов конструктора экземпляров из самого класса. Конструктор выбирается с помощью списка_аргументов и правил разрешения перегрузки в §7.4.3. Набор конструкторов экземпляров – кандидатов состоит из всех доступных конструкторов экземпляров, объявленных в самом классе. Если этот набор пуст или если не удается идентифицировать один наилучший конструктор экземпляров, возникает ошибка времени компиляции. Если объявление конструктора экземпляров включает инициализатор конструктора, вызывающий сам конструктор, возникает ошибка времени компиляции.

Если у конструктора экземпляров нет инициализатора конструктора, неявно предоставляется инициализатор конструктора вида base(). Таким образом, объявление конструктора экземпляров вида

C(...) {...}

в точности эквивалентно

C(...): base() {...}

Область видимости параметров, заданных списком_формальных параметров объявления конструктора экземпляров, включает инициализатор конструктора этого объявления. Таким образом, инициализатору конструктора разрешен доступ к параметрам конструктора. Пример.

class A
{
public A(int x, int y) {}
}

class B: A
{
public B(int x, int y): base(x + y, x - y) {}
}

Инициализатор конструктора экземпляров не может иметь доступ к создаваемому экземпляру. Поэтому ссылка на this в выражении аргумента инициализатора конструктора, как и ссылка в выражении аргумента на любой член экземпляра посредством простого_имени, является ошибкой времени компиляции.

10.11.2 Инициализаторы переменных экземпляров

Если у конструктора экземпляра нет инициализатора конструктора или есть инициализатор конструктора вида base(...), этот конструктор неявно выполняет инициализации, указанные инициализаторами_переменных полей экземпляра, объявленных в его классе. Это соответствует последовательности присваиваний, выполняемых непосредственно при входе в конструктор и перед неявным вызовом конструктора прямого базового класса. Инициализаторы переменных выполняются в текстовом порядке их появления в объявлении класса.

10.11.3 Выполнение конструктора

Инициализаторы переменных преобразуются в операторы присваивания, и эти операторы присваивания выполняются перед вызовом конструктора экземпляров базового класса. Этот порядок обеспечивает инициализацию всех полей экземпляра их инициализаторами переменных до выполнения каких-либо операторов, имеющих доступ к этому экземпляру.

В примере

using System;

class A
{
public A() {
PrintFields();
}

public virtual void PrintFields() {}

}

class B: A
{
int x = 1;
int y;

public B() {
y = -1;
}

public override void PrintFields() {
Console.WriteLine("x = {0}, y = {1}", x, y);
}
}

когда new B() используется для создания экземпляра B, создается следующий вывод:

x = 1, y = 0

Значение x равно 1, потому что инициализатор переменной выполняется до вызова конструктора экземпляров базового класса. Однако значение y равно 0 (значение по умолчанию для int), потому что присваивание для y выполняется только после возврата из конструктора базового класса.

Полезно считать инициализаторы переменных экземпляра операторами, которые автоматически вставляются перед телом_конструктора. В примере

using System;
using System.Collections;

class A
{
int x = 1, y = -1, count;

public A() {
count = 0;
}

public A(int n) {
count = n;
}
}

class B: A
{
double sqrt2 = Math.Sqrt(2.0);
ArrayList items = new ArrayList(100);
int max;

public B(): this(100) {
items.Add("default");
}

public B(int n): base(n – 1) {
max = n;
}
}

содержится несколько инициализаторов переменных, а также содержатся инициализаторы конструкторов обоих видов (base и this). Этот пример соответствует коду, показанному ниже, где каждый комментарий указывает автоматически вставляемый оператор (синтаксис, используемый для автоматически вставляемых вызовов конструктора не является допустимым, а служит только для пояснения механизма).

using System.Collections;

class A
{
int x, y, count;

public A() {
x = 1; // Инициализатор переменной
y = -1; // Инициализатор переменной
object(); // Вызов конструктора object()
count = 0;
}

public A(int n) {
x = 1; // Инициализатор переменной
y = -1; // Инициализатор переменной
object(); // Вызов конструктора object()
count = n;
}
}

class B: A
{
double sqrt2;
ArrayList items;
int max;

public B(): this(100) {
B(100); // Вызов конструктора B(int)
items.Add("default");
}

public B(int n): base(n – 1) {
sqrt2 = Math.Sqrt(2.0); // Инициализатор переменной
items = new ArrayList(100); // Инициализатор переменной
A(n – 1); // Вызов конструктора A(int)
max = n;
}
}

10.11.4 Конструкторы по умолчанию

Если класс не содержит объявления конструкторов экземпляров, автоматически предоставляется конструктор экземпляров по умолчанию. Этот конструктор по умолчанию просто вызывает не имеющий параметров конструктор прямого базового класса. Если в прямом базовом классе нет доступного конструктора экземпляров, не имеющего параметров, возникает ошибка времени компиляции. Если класс абстрактный, то объявленная доступность для конструктора по умолчанию защищена. Иначе объявленная доступность для конструктора по умолчанию является общей. Таким образом, конструктор по умолчанию всегда имеет вид

protected C(): base() {}

или

public C(): base() {}

где C – это имя класса.

В примере

class Message
{
object sender;
string text;
}

предоставлен конструктор по умолчанию, так как класс не содержит объявления конструкторов экземпляров. Так, этот пример в точности эквивалентен следующему

class Message
{
object sender;
string text;

public Message(): base() {}
}

10.11.5 Закрытые конструкторы

Если в классе T объявлены только закрытые конструкторы экземпляров, для классов вне программного текста T невозможно вывести из T или непосредственно создать экземпляры T. Так, если класс содержит только статические члены и не предназначен для создания экземпляров, добавление пустого закрытого конструктора экземпляров предотвратит создание экземпляров. Например:

public class Trig
{
private Trig() {} // Предотвратить создание экземпляров

public const double PI = 3.14159265358979323846;

public static double Sin(double x) {...}
public static double Cos(double x) {...}
public static double Tan(double x) {...}
}

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

10.11.6 Необязательные параметры конструктора экземпляров

Вид this(...) инициализатора конструктора обычно используется в сочетании с перегрузкой для реализации необязательных параметров конструктора экземпляров. В примере

class Text
{
public Text(): this(0, 0, null) {}

public Text(int x, int y): this(x, y, null) {}

public Text(int x, int y, string s) {
// Фактическая реализация конструктора
}
}

первые два конструктора экземпляров только предоставляют значения по умолчанию для отсутствующих аргументов. Оба используют инициализатор конструктора this(...) для вызова третьего конструктора экземпляров, который действительно выполняет работу по инициализации нового экземпляра. Это действие соответствует действию необязательных параметров конструктора:

Text t1 = new Text(); // То же, что Text(0, 0, null)
Text t2 = new Text(5, 10); // То же, что Text(5, 10, null)
Text t3 = new Text(5, 20, "Hello");

10.12 Статические конструкторы

Статический конструктор – это член, реализующий действия, необходимые для инициализации закрытого типа класса. Статические конструкторы объявляются с помощью объявлений_статического_конструктора:

объявление_статического_конструктора:
атрибутынеоб модификаторы_статического_конструктора идентификатор ( ) тело_статического_конструктора

модификаторы_статического_конструктора:
externнеоб static
static externнеоб

тело_статического_конструктора:
блок
;

Объявление_статического_конструктора может включать набор атрибутов (§17) и модификатор extern (§10.6.7).

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

Если в объявление статического конструктора включен модификатор extern, статический конструктор называется внешним статическим конструктором. Так как объявление внешнего статического конструктора не предоставляет фактическую реализацию, его тело_статического_конструктора состоит из точки с запятой. Для всех других объявлений статического конструктора тело_статического_конструктора состоит из блока, в котором указаны операторы, которые необходимо выполнить, чтобы инициализировать класс. Это в точности соответствует телу_метода статического метода с типом возвращаемого значения void (§10.6.10).

Статические конструкторы не наследуются и их нельзя вызвать непосредственно.

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

· создан экземпляр типа класса;

· возникла ссылка на любой статический член типа класса.

Если класс содержит метод Main (§3.1), в котором начинается исполнение, статический конструктор для этого класса выполняется до вызова метода Main.

Для инициализации нового закрытого типа класса сначала создается новый набор статических полей (§10.5.1) для этого особого типа класса. Каждое из статических полей инициализируется своим значением по умолчанию (§5.2). Затем выполняются инициализаторы статических полей (§10.4.5.1) для этих статических полей. Наконец выполняется статический конструктор.

 

Пример

using System;

class Test
{
static void Main() {
A.F();
B.F();
}
}

class A
{
static A() {
Console.WriteLine("Init A");
}
public static void F() {
Console.WriteLine("A.F");
}
}

class B
{
static B() {
Console.WriteLine("Init B");
}
public static void F() {
Console.WriteLine("B.F");
}
}

должен дать на выходе:

Init A
A.F
Init B
B.F

так как выполнение статического конструктора класса A запускается по обращению к A.F, а выполнение статического конструктора класса B запускается обращением к B.F.

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

Пример

using System;

class A
{
public static int X;

static A() {
X = B.Y + 1;
}
}

class B
{
public static int Y = A.X + 1;

static B() {}

static void Main() {
Console.WriteLine("X = {0}, Y = {1}", A.X, B.Y);
}
}

дает в результате

X = 1, Y = 2

Для выполнения метода Main система сначала выполняет инициализатор для B.Y, до статического конструктора класса B. Инициализатор Y вызывает запуск статического конструктора класса A, так как имеется ссылка на значение A.X. Статический конструктор A в свою очередь продолжает вычислять значение X и при этом вычислении выбирает значение по умолчанию для Y, которое равно нулю. A.X таким образом инициализируется значением 1. Процесс выполнения для класса A инициализаторов статических полей и статического конструктора затем завершается, возвращаясь к вычислению начального значения Y, результат которого становится равным 2.

Так как статический конструктор выполняется ровно один раз для каждого закрытого сформированного типа класса, он является удобным местом для принудительных проверок во время выполнения параметров-типов, которые не удается проверить с помощью ограничений во время компиляции (§10.1.5). Например, следующий тип использует статический конструктор для проверки, что аргументом типа является перечисляемый тип:

class Gen<T> where T: struct
{
static Gen() {
if (!typeof(T).IsEnum) {
throw new ArgumentException("T должен быть enum");
}
}
}

10.13 Деструкторы

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

объявление_деструктора:
атрибутынеоб externнеоб ~ идентификатор ( ) тело_деструктора

тело_деструктора:
блок
;

Объявление_деструктора может включать набор атрибутов (§17).

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

Если в объявление деструктора включен модификатор extern, деструктор называется внешним деструктором. Поскольку объявление внешнего деструктора не предоставляет фактическую реализацию, его тело_деструктора состоит из точки с запятой. Во всех других деструкторах тело_деструктора состоит из блока, в котором указаны операторы для уничтожения экземпляра класса. Тело_деструктора в точности соответствует телу_метода метода экземпляра с типом возвращаемого значения void (§10.6.10).

Деструкторы не наследуются. Таким образом, класс не имеет других деструкторов, кроме того, который может быть объявлен в этом классе.

Так как деструктор должен не иметь параметров, его нельзя перегрузить, поэтому класс может иметь не более одного деструктора.

Деструкторы вызываются автоматически, их нельзя вызвать явно. Экземпляр становится пригодным для уничтожения, когда более невозможно использование этого экземпляра любым кодом. Выполнение деструктора для экземпляра может произойти в любое время после того, как экземпляр становится пригодным для уничтожения. Когда экземпляр уничтожен, вызываются деструкторы в цепочке наследования этого экземпляра по порядку, от старшего производного к младшему. Деструктор может выполняться в любом потоке. Дальнейшее обсуждение правил, определяющих, когда и как выполняется деструктор, см. в §3.9.

На выходе примера

using System;

class A
{
~A() {
Console.WriteLine("деструктор A");
}
}

class B: A
{
~B() {
Console.WriteLine("деструктор B");
}
}

class Test
{
static void Main() {
B b = new B();
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}

получается

деструктор B
деструктор A

так как деструкторы в цепочке наследования вызываются по порядку, от самого старшего производного до самого младшего.

Деструкторы реализуются переопределением виртуального метода Finalize для System.Object. Программам C# запрещено переопределять этот метод или вызывать его (или его переопределения) непосредственно. Например, программа

class A
{
override protected void Finalize() {} // ошибка

public void F() {
this.Finalize(); // ошибка
}
}

содержит две ошибки.

Поведение компилятора такое, как будто этот метод и его переопределения вовсе не существуют. Таким образом, эта программа:

class A
{
void Finalize() {} // разрешено
}

допустима, а показанный метод скрывает метод Finalize объекта System.Object.

Обсуждение поведения, когда исключение вызывается из деструктора, см. в §16.3.

10.14 Итераторы

Член функции (§7.4), реализованный с помощью блока итератора(§8.2), называется итератором.

Блок итератора можно использовать как тело члена функции, пока тип возвращаемого значения соответствующего члена функции является одним из интерфейсов перечислителя (§10.14.1) или одним из перечислимых интерфейсов (§10.14.2). Он может встречаться как тело_метода, тело_оператора или тело_метода_доступа, тогда как события, конструкторы экземпляров, статические конструкторы и деструкторы не могут быть реализованы как итераторы.

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

10.14.1 Интерфейсы перечислителя

Интерфейсы перечислителя – это не универсальный интерфейс System.Collections.IEnumerator и все экземпляры универсального интерфейса System.Collections.Generic.IEnumerator<T>. Эти интерфейсы в этой главе для краткости обозначаем как IEnumerator и IEnumerator<T> соответственно.

10.14.2 Перечислимые интерфейсы

Перечислимые интерфейсы – это не универсальный интерфейс System.Collections.IEnumerable и все экземпляры универсального интерфейса System.Collections.Generic.IEnumerable<T>. Эти интерфейсы в этой главе для краткости обозначаем как IEnumerable и IEnumerable<T> соответственно.

Yield

Итератор создает последовательность значений одного и того же типа. Этот тип называется типом yield итератора.

· Типом выдачи итератора, который возвращает IEnumerator или IEnumerable, является object.

· Типом выдачи итератора, который возвращает IEnumerator<T> или IEnumerable<T>, является T.

10.14.4 Объекты перечислителя

Если член функции, возвращающий тип интерфейса перечислителя, реализован с помощью блока итератора, при вызове члена функции не происходит немедленное выполнение кода в блоке итератора. Вместо этого создается и возвращается объект перечислителя. Этот объект инкапсулирует код, указанный в блоке итератора, а выполнение кода в блоке итератора происходит тогда, когда вызывается метод MoveNext объекта перечислителя. Объект перечислителя имеет следующие характеристики:

· Он реализует IEnumerator и IEnumerator<T>, где T – это тип yield итератора.

· Он реализует System.IDisposable.

· Он инициализируется копией значений аргумента (при наличии) и значением экземпляра, переданным члену функции.

· У него есть четыре потенциальных состояния, before, running, suspended и after, начальное состояние before.

Обычно объект перечислителя является экземпляром созданного компилятором класса перечислителя, который инкапсулирует код в блоке итератора и реализует интерфейсы перечислителя, но возможны и другие методы реализации. Если класс перечислителя создан компилятором, этот класс будет вложен, прямо или косвенно, в класс, содержащий член функции, у него будет частная доступность и имя, зарезервированное для использования компилятором (§2.4.2).

Объект перечислителя может реализовать больше интерфейсов, чем указано выше.

В следующих разделах описано точное поведение членов MoveNext, Current и Dispose реализаций интерфейсов IEnumerable и IEnumerable<T>, предоставляемых объектом перечислителя.

Обратите внимание, что объекты перечислителя не поддерживают метод IEnumerator.Reset. Вызов этого метода приводит к исключению System.NotSupportedException.

MoveNext

· Если состояние объекта… o состояние… o инициализируются…

Current

Если объект… Для…

Dispose

· Если состояние объекта… · Если… · Если состояние…

10.14.5 Перечислимые объекты

Если член функции, возвращающий перечислимый тип интерфейса, реализован с помощью блока итератора, при вызове члена функции не происходит немедленное выполнение кода в блоке итератора. Вместо этого создается и возвращается перечислимый объект. Метод GetEnumerator перечислимого объекта возвращает объект перечислителя, который инкапсулирует код, указанный в блоке итератора, а выполнение кода в блоке итератора происходит при вызове метода MoveNext объекта перечислителя. Перечислимый объект имеет следующие характеристики:

· Он реализует IEnumerable и IEnumerable<T>, где T – тип выдачи итератора.

· Он инициализируется копией значений аргумента (при наличии) и значением экземпляра, переданным члену функции.

Обычно перечислимый объект является экземпляром созданного компилятором перечислимого класса, который инкапсулирует код в блоке итератора и реализует перечислимые интерфейсы, но возможны и другие методы реализации. Если перечислимый класс создан компилятором, этот класс будет вложен, прямо или косвенно, в класс, содержащий член функции, у него будет частная доступность и имя, зарезервированное для использования компилятором (§2.4.2).

Перечислимый объект может реализовать больше интерфейсов, чем указано выше. В частности, перечислимый объект может также реализовать IEnumerator и IEnumerator<T>, позволяя ему выступать в роли как перечислимого, так и перечислителя. При таком типе реализации при первом вызове метода GetEnumerator перечислимого объекта возвращается сам перечислимый объект. Последующие вызовы GetEnumerator перечислимого объекта, если они есть, возвращают копию этого перечислимого объекта. Таким образом, каждый возвращенный перечислитель имеет свое собственное состояние и изменения в одном перечислителе не влияют на другой.

GetEnumerator

10.14.6 Пример реализации

В этом разделе описана возможная реализация итераторов при использования стандартных конструкций C#. В основе реализации, описанной в этом разделе, лежат принципы, используемые в компиляторе Microsoft C#, однако она никоим образом не является обязательной или единственной возможной.

Следующий класс Stack<T> реализует метод GetEnumerator с помощью итератора. Итератор перечисляет элементы стека в нисходящем порядке.

using System;
using System.Collections;
using System.Collections.Generic;

class Stack<T>: IEnumerable<T>
{
T[] items;
int count;

public void Push(T item) {
if (items == null) {
items = new T[4];
}
else if (items.Length == count) {
T[] newItems = new T[count * 2];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
items[count++] = item;
}

public T Pop() {
T result = items[--count];
items[count] = default(T);
return result;
}

public IEnumerator<T> GetEnumerator() {
for (int i = count - 1; i >= 0; --i) yield return items[i];
}
}

Метод GetEnumerator можно транслировать в экземпляр класса перечислителя, созданного компилятором и инкапсулирующего код в блоке итератора, как показано в следующем примере.

class Stack<T>: IEnumerable<T>
{
...

public IEnumerator<T> GetEnumerator() {
return new __Enumerator1(this);
}

class __Enumerator1: IEnumerator<T>, IEnumerator
{
int __state;
T __current;
Stack<T> __this;
int i;

public __Enumerator1(Stack<T> __this) {
this.__this = __this;
}

public T Current {
get { return __current; }
}

object IEnumerator.Current {
get { return __current; }
}

public bool MoveNext() {
switch (__state) {
case 1: goto __state1;
case 2: goto __state2;
}
i = __this.count - 1;
__loop:
if (i < 0) goto __state2;
__current = __this.items[i];
__state = 1;
return true;
__state1:
--i;
goto __loop;
__state2:
__state = 2;
return false;
}

public void Dispose() {
__state = 2;
}

void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}

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

Следующий пример служит для печати простой таблицы умножения целых чисел от 1 до 10. В этом примере метод FromTo возвращает перечислимый объект и реализуется с помощью итератора.

using System;
using System.Collections.Generic;

class Test
{
static IEnumerable<int> FromTo(int from, int to) {
while (from <= to) yield return from++;
}

static void Main() {
IEnumerable<int> e = FromTo(1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}

Метод FromTo можно транслировать в экземпляр класса перечислителя, созданного компилятором и инкапсулирующего код в блоке итератора, как показано в следующем примере.

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

class Test
{
...

static IEnumerable<int> FromTo(int from, int to) {
return new __Enumerable1(from, to);
}

class __Enumerable1:
IEnumerable<int>, IEnumerable,
IEnumerator<int>, IEnumerator
{
int __state;
int __current;
int __from;
int from;
int to;
int i;

public __Enumerable1(int __from, int to) {
this.__from = __from;
this.to = to;
}

public IEnumerator<int> GetEnumerator() {
__Enumerable1 result = this;
if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
result = new __Enumerable1(__from, to);
result.__state = 1;
}
result.from = result.__from;
return result;
}

IEnumerator IEnumerable.GetEnumerator() {
return (IEnumerator)GetEnumerator();
}

public int Current {
get { return __current; }
}

object IEnumerator.Current {
get { return __current; }
}

public bool MoveNext() {
switch (__state) {
case 1:
if (from > to) goto case 2;
__current = from++;
__state = 1;
return true;
case 2:
__state = 2;
return false;
default:
throw new InvalidOperationException();
}
}

public void Dispose() {
__state = 2;
}

void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}

Перечислимый класс реализует как перечислимые интерфейсы, так и интерфейсы перечислителя, что позволяет ему служить как перечислимым классом, так и перечислителем. При первом вызове метода GetEnumerator будет возвращен сам перечислимый объект. При последующих вызовах GetEnumerator для перечислимого объекта, при их наличии, будет возвращена копия перечислимого объекта. Таким образом, каждый возвращенный перечислитель имеет собственное состояние, и изменения в одном перечислителе не влияют на другой. Метод Interlocked.CompareExchange используется для обеспечения потокобезопасной операции.

Параметры from и to преобразуются в поля в перечислимом классе. Поскольку параметр from изменяется в блоке итератора, то дополнительно вводится поле __from, которое содержит начальное значение, присваиваемое параметру from в каждом перечислителе.

Метод MoveNext порождает исключение InvalidOperationException, если при его вызове значение __state равно 0. Это позволяет не допустить использования перечислимого объекта в качестве объекта перечислителя без вызова GetEnumerator.

В следующем примере показан класс простого дерева. Класс Tree<T> реализует метод GetEnumerator с помощью итератора. Итератор перечисляет элементы дерева в инфиксном порядке.

using System;
using System.Collections.Generic;

class Tree<T>: IEnumerable<T>
{
T value;
Tree<T> left;
Tree<T> right;

public Tree(T value, Tree<T> left, Tree<T> right) {
this.value = value;
this.left = left;
this.right = right;
}

public IEnumerator<T> GetEnumerator() {
if (left != null) foreach (T x in left) yield x;
yield value;
if (right != null) foreach (T x in right) yield x;
}
}

class Program
{
static Tree<T> MakeTree<T>(T[] items, int left, int right) {
if (left > right) return null;
int i = (left + right) / 2;
return new Tree<T>(items[i],
MakeTree(items, left, i - 1),
MakeTree(items, i + 1, right));
}

static Tree<T> MakeTree<T>(params T[] items) {
return MakeTree(items, 0, items.Length - 1);
}

// После выполнения программы получается следующий результат:
// 1 2 3 4 5 6 7 8 9
// Пн Вт Ср Чт Пт Cб Вс

static void Main() {
Tree<int> ints = MakeTree(1, 2, 3, 4, 5, 6, 7, 8, 9);
foreach (int i in ints) Console.Write("{0} ", i);
Console.WriteLine();

Tree<string> strings = MakeTree(
"Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс");
foreach (string s in strings) Console.Write("{0} ", s);
Console.WriteLine();
}
}

Метод GetEnumerator можно транслировать в экземпляр класса перечислителя, созданного компилятором и инкапсулирующего код в блоке итератора, как показано в следующем примере.

class Tree<T>: IEnumerable<T>
{
...

public IEnumerator<T> GetEnumerator() {
return new __Enumerator1(this);
}

class __Enumerator1 : IEnumerator<T>, IEnumerator
{
Node<T> __this;
IEnumerator<T> __left, __right;
int __state;
T __current;

public __Enumerator1(Node<T> __this) {
this.__this = __this;
}

public T Current {
get { return __current; }
}

object IEnumerator.Current {
get { return __current; }
}

public bool MoveNext() {
try {
switch (__state) {

case 0:
__state = -1;
if (__this.left == null) goto __yield_value;
__left = __this.left.GetEnumerator();
goto case 1;

case 1:
__state = -2;
if (!__left.MoveNext()) goto __left_dispose;
__current = __left.Current;
__state = 1;
return true;

__left_dispose:
__state = -1;
__left.Dispose();

__yield_value:
__current = __this.value;
__state = 2;
return true;

case 2:
__state = -1;
if (__this.right == null) goto __end;
__right = __this.right.GetEnumerator();
goto case 3;

case 3:
__state = -3;
if (!__right.MoveNext()) goto __right_dispose;
__current = __right.Current;
__state = 3;
return true;

__right_dispose:
__state = -1;
__right.Dispose();

__end:
__state = 4;
break;

}
}
finally {
if (__state < 0) Dispose();
}
return false;
}

public void Dispose() {
try {
switch (__state) {

case 1:
case -2:
__left.Dispose();
break;

case 3:
case -3:
__right.Dispose();
break;

}
}
finally {
__state = 4;
}
}

void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}

Создаваемые компилятором временные данные, используемые в операторах foreach, переносятся в поля __left и __right объекта перечислителя. Поле __state объекта перечислителя обновляется таким образом, чтобы при генерации исключения был правильно вызван необходимый метод Dispose(). Обратите внимание, что написание транслируемого кода с простыми операторами foreach невозможно.

11. Структуры

Структуры, как и классы, представляют определенным образом организованные данные. В структурах могут содержаться как данные, так и функции. Однако, в отличие от классов, структуры являются типами значений и для них не требуется выделение памяти из кучи. Переменная с типом структуры непосредственно содержит данные этой структуры, в то время как переменная с типом класса содержит ссылку на данные, которые считаются объектом.

Структуры особенно удобны для работы с небольшим объемом данных, имеющих семантику значения. Хорошими примерами структур являются комплексные числа, точки в системе координат и пары ключ-значение в словаре. Ключевой особенностью таких структур данных является наличие нескольких переменных-членов, для которых не требуется использовать наследование или идентификацию на уровне ссылок и которые удобно реализовывать с использованием семантики значения, когда при присваивании копируется значение, а не ссылка.

Как описано в §4.1.4, доступные в C# простые типы, такие как int, double и bool, фактически являются структурами. Благодаря тому, что эти предопределенные типы являются структурами, в языке C# можно реализовывать новые «простые» типы при помощи переопределения структур и операторов. В конце этой главы приводится два примера таких типов (§11.4).

11.1 Объявления структур

Объявление_структуры является объявлением_типа (§9.6), в котором объявляется новая структура:

объявление_структуры:
атрибуты (н/о) модификаторы_структуры (н/о) partial (н/о) struct идентификатор список_параметров_типа (н/о)
интерфейсы_структуры (н/о) конструкции_ограничений_для_параметров_типа (н/о) тело_структуры ;(н/о)

Объявление_структуры состоит из необязательного набора атрибутов (§17), за которым следует необязательный набор модификаторов_структуры (§11.1.1), необязательный модификатор partial, ключевое слово struct, идентификатор, который указывает имя структуры, необязательная спецификация списка_параметров_типа (§10.1.3), необязательная спецификация интерфейсов_структуры (§11.1.2) ), необязательная спецификация конструкций_ограничений_для_параметров_типа (§10.1.5), тело_структуры (§11.1.4) и точка с запятой (необязательно).

11.1.1 Модификаторы структуры

Объявление_структуры может включать последовательность модификаторов структуры:

модификаторы_структуры:
модификатор_структуры
модификаторы_структуры модификатор_структуры

модификатор_структуры:
new
public
protected
internal
private

Включение одного модификатора в объявление структуры несколько раз приведет к возникновению ошибки времени выполнения.

Модификаторы объявления структуры имеют такое же действие, как модификаторы объявления класса (§10.1).

Partial

11.1.3 Интерфейсы структуры

Объявление структуры может содержать спецификацию интерфейсов_структуры, в этом случае считается, что в структуре реализованы заданные типы интерфейсов.

интерфейсы_структуры:
: список_типов_интерфейсов

Реализация интерфейсов рассматривается более подробно в §13.4.

11.1.4 Тело структуры

Тело_структуры определяет членов структуры.

тело_структуры:
{ объявления_членов_структуры (н/о) }

11.2 Члены структуры

К членам структуры относятся члены, включенные в объявления_членов_структуры и члены, унаследованные из System.ValueType.

объявления_членов_структуры:
объявление_члена_структуры
объявления_членов_структуры объявление_члена_структуры

объявление_членов_структуры:
объявление_константы
объявление_поля
объявление_метода
объявление_свойства
объявление_события
объявление_индексатора
объявление_оператора
объявление_конструктора
объявление_статического_конструктора
объявление_типа

За исключением различий, рассмотренных в §11.3, описания членов класса, содержащиеся в §10.13 — §10.14, справедливы и для членов структуры.

11.3 Различия между классом и структурой

Структуры имеют несколько важных отличий от классов:

· Структуры являются типами значения (§11.3.1).

· Все типы структур неявным образом наследуются из класса System.ValueType (§11.3.2).

· При присваивании переменной с типом структуры создается копия присваиваемого значения (§11.3.3).

· Значением по умолчанию для структуры является значение, при котором все поля типов значения устанавливаются в соответствующие значения по умолчанию, а все поля ссылочного типа — в значение null (§11.3.4).

· Операции упаковки и распаковки используются для выполнения преобразования между типом структуры и объектом object (§11.3.5).

· Ключевое слово this в структурах имеет другой смысл (§7.5.7).

· Объявления полей экземпляров для структуры не могут включать инициализаторы переменных (§11.3.7).

· В структуре не разрешается объявлять конструктор экземпляров без параметров (§11.3.8).

· В структуре не может быть объявлен деструктор (§11.3.9).

11.3.1 Семантика значений

Структуры относятся к типам значения (§4.1) и считаются имеющими семантику значения. Наоборот, классы относятся к ссылочным типам (§4.2) и считаются имеющими семантику ссылок.

Переменная с типом структуры непосредственно содержит данные структуры, в то время как переменная с типом класса содержит ссылку на данные, которые являются объектом. Если структура B содержит поле экземпляра с типом A, который является типом структуры, зависимость A от B приведет к возникновению ошибки компиляции. Структура X имеет прямую зависимость от структуры Y в том случае, если X содержит поле экземпляра с типом Y. В соответствии с этим определением полный набор структур, по отношению к которым структура является зависимой, представляет собой транзитивное замыкание связи с типом имеет прямую зависимость от. Например, код

struct Node
{
int data;

Node next; // ошибка, структура Node имеет прямую зависимость от самой себя

}

является ошибкой, поскольку структура Node содержит поле экземпляра с собственным типом. Другой пример. Код

struct A { B b; }

struct B { C c; }

struct C { A a; }

является ошибкой, поскольку типы A, B и C зависят друг от друга.

В классах две переменные могут ссылаться на один объект, в результате чего действия с одной переменной могут повлиять на объект, на который ссылается другая переменная. В структурах каждая из переменных имеет собственную копию данных (за исключением переменных параметров ref и out) и действия с одной из переменных не могут повлиять на другую переменную. Более того, поскольку структуры не относятся к ссылочным типам, значения с типом структуры не могут быть равны null.

При объявлении

struct Point
{
public int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

выполнение фрагмента кода

Point a = new Point(10, 10);
Point b = a;
a.x = 100;
System.Console.WriteLine(b.x);

возвратит значение 10. При присваивании значения переменной a переменной b создается копия этого значения, поэтому на переменную b не влияет изменение значения переменной a.x. Если бы структура Point была объявлена как класс, выходным значением было бы 100, поскольку переменные a и b ссылались бы на один и тот же объект.

11.3.2 Наследование

Все типы структур неявным образом наследуются из класса System.ValueType, который, в свою очередь, наследуется из класса object. В объявлении структуры может быть указан список реализованных интерфейсов, однако здесь не допускается указание базового класса.

Тип структуры никогда не является абстрактным и всегда неявным образом запечатан. Поэтому в объявлении структуры не допускаются модификаторы abstract и sealed.

Так как для структур не поддерживается наследование, член структуры не может иметь объявленный уровень доступа protected или protected internal.

Функции-члены в структуре не могут иметь модификаторы abstract или virtual, а модификатор override допускается только для переопределения методов, унаследованных из класса System.ValueType.

11.3.3 Присваивание

При выполнении присваивания переменной с типом структуры создается копия присваиваемого значения. В отличие от этого при выполнении присваивания в переменную с типом класса копируется ссылка, а не сам объект, на который указывает эта ссылка.

Аналогичным образом при передаче структуры в качестве параметра по значению или возвращения ее в результате выполнения функции-члена создается копия структуры. Структура может передаваться в функцию-член по ссылке с использованием параметра ref или out.

Если целевым объектом операции присваивания является свойство или индексатор структуры, в качестве переменной должно быть указано выражение экземпляра, сопоставленное доступу к индексатору или свойству. Если выражение экземпляра классифицировано как значение, возникнет ошибка компиляции. Более подробно эта тема рассматривается в §7.16.1.

11.3.4 Значения по умолчанию

Как рассматривалось в §5.2, некоторые виды переменных при создании автоматически инициализируются значениями по умолчанию. Для переменных с типом класса или другими ссылочными типами используется значение null. Однако в связи с тем, что структуры имеют тип значения и не могут быть равны null, значение по умолчанию для структуры является значением, полученным путем установки для всех полей с типом значения соответствующих значений по умолчанию, а для всех полей с ссылочным типом — значения null.

Для объявленной выше структуры Point строка

Point[] a = new Point[100];

выполняет инициализацию каждой переменной Point в массиве значением, полученным путем установки для полей x и y нулевых (0) значений.

Значение по умолчанию структуры соответствует значению, возвращаемому конструктором структуры по умолчанию (§4.1.2). Для структуры, в отличие от класса, не допускается объявление конструктора экземпляра без параметров. Вместо этого каждая структура неявно содержит конструктор экземпляра без параметров, который всегда возвращает значение, полученное путем установки для всех полей с типом значения соответствующих значений по умолчанию, а для всех полей с ссылочным типом — значения null.

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

using System;

struct KeyValuePair
{
string key;
string value;

public KeyValuePair(string key, string value) {
if (key == null || value == null) throw new ArgumentException();
this.key = key;
this.value = value;
}
}

пользовательский конструктор экземпляра обеспечивает защиту от пустых значений только при явном вызове. В случае, когда переменная KeyValuePair инициализируются значением по умолчанию, поля key и value имеют значение «null», и в структуре следует предусмотреть обработку такого состояния.

11.3.5 Упаковка и распаковка

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

Так как структуры не относятся к ссылочным типам, для типов структуры эти операции реализуются по-другому. При преобразовании значения с типом структуры в тип object или в тип интерфейса, реализуемый этой структурой, происходит операция упаковки. Точно так же при обратном преобразовании значения с типом object или значения с типом интерфейса в тип структуры выполняется операция распаковки. Ключевое отличие от аналогичных операций с типами класса состоит в том, что при упаковке и распаковке выполняется копирование значения структуры в упакованный экземпляр или из такого экземпляра. Таким образом, после выполнения операции упаковки или распаковки изменения, внесенные в распакованную структуру, не отражаются в упакованной структуре.

Если в структуре переопределяется виртуальный метод, унаследованный из класса System.Object (например, Equals, GetHashCode или ToString), вызов этого виртуального метода в экземпляре типа структуры не ведет к выполнению упаковки. Это правило действует даже в том случае, когда структура используется в качестве параметра типа и вызов происходит в экземпляре с типом параметра типа. Например:

using System;

struct Counter
{
int value;

public override string ToString() {
value++;
return value.ToString();
}
}

class Program
{
static void Test<T>() where T: new() {
T x = new T();
Console.WriteLine(x.ToString());
Console.WriteLine(x.ToString());
Console.WriteLine(x.ToString());
}

static void Main() {
Test<Counter>();
}
}

В результате выполнения программы будет получен следующий результат:

1
2
3

Несмотря на то, что использование метода ToString для выполнения побочных действий является плохим стилем, в этом примере демонстрируется, что при трех вызовах метода x.ToString() упаковка не выполнялась.

Аналогичным образом упаковка не выполняется неявным образом при доступе к члену с ограниченным параметром-типом. Например, предположим, что интерфейс ICounter содержит метод Increment, при помощи которого можно изменять значения. Если метод ICounter используется в качестве ограничения, реализация метода Increment вызывается со ссылкой на переменную, для которой был вызван метод Increment, а не для упакованной копии.

using System;

interface ICounter
{
void Increment();
}

struct Counter: ICounter
{
int value;

public override string ToString() {
return value.ToString();
}

void ICounter.Increment() {
value++;
}
}

class Program
{
static void Test<T>() where T: ICounter, new() {
T x = new T();
Console.WriteLine(x);
x.Increment(); // Изменение x
Console.WriteLine(x);
((ICounter)x).Increment(); // Изменение упакованной копии x
Console.WriteLine(x);
}

static void Main() {
Test<Counter>();
}
}

При первом вызове метода Increment изменяется значение переменной x. Это не равноценно второму вызову метода Increment, при котором изменяется значение упакованной копии x. Таким образом, в результате выполнения программы будет получен следующий результат:

0
1
1

Дополнительные сведения об упаковке и распаковке см. в §4.3.

This

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

В конструкторе экземпляра структуры ключевое слово this соответствует параметру out типа структуры, а в функции-члене экземпляра структуры ключевое слово this соответствует параметру ref типа структуры. В обоих случаях this считается переменной, что позволяет изменить всю структуру, для которой была вызвана эта функция-член, путем присваивания в this или передачи this в качестве параметра ref или out.

11.3.7 Инициализаторы полей

Как рассматривалось в §11.3.4, значение структуры по умолчанию состоит из значения, полученного путем установки для всех полей с типом значения соответствующих значений по умолчанию, а для всех полей ссылочного типа — значения null. По этой причине в структуре объявления полей экземпляра не могут содержаться инициализаторы переменных. Это ограничение действует только в отношении полей экземпляра. Статические поля структуры могут содержать инициализаторы переменных.

Например, код

struct Point
{
public int x = 1; // Ошибка, инициализатор недопустим
public int y = 1; // Ошибка, инициализатор недопустим
}

является неправильным, так как объявления полей экземпляра содержат инициализаторы переменных.

11.3.8 Конструкторы

В отличие от класса, в структуре нельзя объявить конструктор экземпляра без параметров. Вместо этого каждая структура неявно содержит конструктор экземпляра без параметров, который всегда возвращает значение, полученное путем установки для всех полей с типом значения соответствующих значений по умолчанию, а для всех полей с ссылочным типом — значения null (§4.1.2). В структуре можно объявлять конструкторы экземпляров с параметрами. Например:

struct Point
{
int x, y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

В приведенном выше объявлении операторы

Point p1 = new Point();

Point p2 = new Point(0, 0);

создают объекты Point, свойства которых x и y инициализированы нулевыми значениями.

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

Если в конструкторе экземпляра структуры не указан инициализатор конструктора, переменная this соответствует параметру out типа структуры, при этом, аналогично параметру out, объект this должен быть определенно присвоен (§5.3) в каждой точке возвращения из конструктора. Если в конструкторе экземпляра структуры указан инициализатор конструктора, переменная this соответствует параметру ref с типом структуры и, аналогично параметру ref, переменная this считается определенно присвоенной в точке входа в тело конструктора. Рассмотрим приведенную ниже реализацию конструктора:

struct Point
{
int x, y;

public int X {
set { x = value; }
}

public int Y {
set { y = value; }
}

public Point(int x, int y) {
X = x; // Ошибка, переменная this пока не является определенно присвоенной
Y = y; // Ошибка, переменная this пока не является определенно присвоенной
}
}

Ни одна из функций-членов экземпляра (включая методы доступа «set» для свойств X и Y) не может быть вызвана до тех пор, пока все поля создаваемой структуры не будут определенно присвоены. Однако, если бы объект Point был классом, а не структурой, реализация конструктора экземпляра была бы разрешена.

11.3.9 Деструкторы

В структуре не разрешается объявлять деструктор.

11.3.10 Статические конструкторы

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

· Ссылка на экземпляр-член с типом структуры.

· Ссылка на статический член с типом структуры.

· Вызов явным образом объявленного конструктора с типом структуры.

Создание значений по умолчанию (§11.3.4) с типом структуры не ведет к вызову статического конструктора. В качестве примера можно указать начальные значения элементов массива.

11.4 Примеры структур

Ниже приводится два примера использования типов struct для создания типов, которые могут использоваться аналогично встроенным типам языка, но имеют измененную семантику.

11.4.1 Тип целочисленного значения в базе данных

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

using System;

public struct DBInt
{
// Член Null представляет неизвестное значение с типом DBInt.

public static readonly DBInt Null = new DBInt();

// Если определенное поле имеет значение true, этот объект DBInt представляет известное значение,
// которое хранится в данном поле значения. Если определенное поле имеет значение false,
// этот объект DBInt представляет неизвестное значение, при этом поле значения равно 0.

int value;
bool defined;

// Закрытый конструктор экземпляра. Создает объект с типом DBInt и известным значением.

DBInt(int value) {
this.value = value;
this.defined = true;
}

// Свойство IsNull имеет значение true, если данный объект DBInt представляет неизвестное значение.

public bool IsNull { get { return !defined; } }

// Свойство Value содержит известное значение данного объекта DBInt или 0, если этот объект
// DBInt представляет неизвестное значение.

public int Value { get { return value; } }

// Неявное преобразование из int в DBInt.

public static implicit operator DBInt(int x) {
return new DBInt(x);
}

// Явное преобразование из DBInt в int. Вызывает исключение, если
// заданный объект DBInt представляет неизвестное значение.

public static explicit operator int(DBInt x) {
if (!x.defined) throw new InvalidOperationException();
return x.value;
}

public static DBInt operator +(DBInt x) {
return x;
}

public static DBInt operator -(DBInt x) {
return x.defined ? -x.value : Null;
}

public static DBInt operator +(DBInt x, DBInt y) {
return x.defined && y.defined? x.value + y.value: Null;
}

public static DBInt operator -(DBInt x, DBInt y) {
return x.defined && y.defined? x.value - y.value: Null;
}

public static DBInt operator *(DBInt x, DBInt y) {
return x.defined && y.defined? x.value * y.value: Null;
}

public static DBInt operator /(DBInt x, DBInt y) {
return x.defined && y.defined? x.value / y.value: Null;
}

public static DBInt operator %(DBInt x, DBInt y) {
return x.defined && y.defined? x.value % y.value: Null;
}

public static DBBool operator ==(DBInt x, DBInt y) {
return x.defined && y.defined? x.value == y.value: DBBool.Null;
}

public static DBBool operator !=(DBInt x, DBInt y) {
return x.defined && y.defined? x.value != y.value: DBBool.Null;
}

public static DBBool operator >(DBInt x, DBInt y) {
return x.defined && y.defined? x.value > y.value: DBBool.Null;
}

public static DBBool operator <(DBInt x, DBInt y) {
return x.defined && y.defined? x.value < y.value: DBBool.Null;
}

public static DBBool operator >=(DBInt x, DBInt y) {
return x.defined && y.defined? x.value >= y.value: DBBool.Null;
}

public static DBBool operator <=(DBInt x, DBInt y) {
return x.defined && y.defined? x.value <= y.value: DBBool.Null;
}

public override bool Equals(object obj) {
if (!(obj is DBInt)) return false;
DBInt x = (DBInt)obj;
return value == x.value && defined == x.defined;
}

public override int GetHashCode() {
return value;
}

public override string ToString() {
return defined? value.ToString(): “DBInt.Null”;
}
}

11.4.2 Логический тип базы данных

Представленная ниже структура DBBool реализует трехзначный логический тип. Возможными значениями этого типа являются значения DBBool.True, DBBool.False и DBBool.Null, где член Null указывает неизвестное значение. Подобные трехзначные логические типы часто используются в базах данных.

using System;

public struct DBBool
{
// Три возможных значения DBBool.

public static readonly DBBool Null = new DBBool(0);
public static readonly DBBool False = new DBBool(-1);
public static readonly DBBool True = new DBBool(1);

// Закрытое поле, в котором хранятся значения –1, 0 и 1 для False, Null и True.

sbyte value;

// Закрытый конструктор экземпляра. Значение этого параметра должно быть –1, 0 или 1.

DBBool(int value) {
this.value = (sbyte)value;
}

// Свойства для проверки значения объекта DBBool. Возвращает true, если этот объект
// DBBool имеет конкретное значение, в обратном случае — значение false.

public bool IsNull { get { return value == 0; } }

public bool IsFalse { get { return value < 0; } }

public bool IsTrue { get { return value > 0; } }

// Неявное преобразование из bool в DBBool. Значение True сопоставлено DBBool.True, а значение
// false — значению DBBool.False.

public static implicit operator DBBool(bool x) {
return x? True: False;
}

// Явное преобразование из DBBool в bool. Вызывает исключение, если
// конкретный объект DBBool имеет значение Null, в обратном случае возвращает true или false.

public static explicit operator bool(DBBool x) {
if (x.value == 0) throw new InvalidOperationException();
return x.value > 0;
}

// Оператор равенства. Возвращает Null, если один из операторов равен Null, в обратном случае
// возвращает True или False.

public static DBBool operator ==(DBBool x, DBBool y) {
if (x.value == 0 || y.value == 0) return Null;
return x.value == y.value? True: False;
}

// Оператор неравенства. Возвращает Null, если один из операторов равен Null, в обратном случае
// возвращает True или False.

public static DBBool operator !=(DBBool x, DBBool y) {
if (x.value == 0 || y.value == 0) return Null;
return x.value != y.value? True: False;
}

// Оператор логического отрицания. Возвращает True, если операнд имеет значение False, Null,
// если операнд имеет значение Null, либо False, если операнд имеет значение True.

public static DBBool operator !(DBBool x) {
return new DBBool(-x.value);
}

// Оператор логического AND. Возвращает False, если один из операндов имеет значение False,
// значение Null, если один из операндов имеет значение Null, в обратном случае — True.

public static DBBool operator &(DBBool x, DBBool y) {
return new DBBool(x.value < y.value? x.value: y.value);
}

// Оператор логического OR. Возвращает True, если один из операндов имеет значение True,
// значение Null, если один из операндов имеет значение Null, в обратном случае — False.

public static DBBool operator |(DBBool x, DBBool y) {
return new DBBool(x.value > y.value? x.value: y.value);
}

// Оператор определенной истины. Возвращает True, если операнд имеет значение True, в обратном случае
// значение False.

public static bool operator true(DBBool x) {
return x.value > 0;
}

// Оператор определенной лжи. Возвращает True, если операнд имеет значение False, в обратном случае
// значение False.

public static bool operator false(DBBool x) {
return x.value < 0;
}

public override bool Equals(object obj) {
if (!(obj is DBBool)) return false;
return value == ((DBBool)obj).value;
}

public override int GetHashCode() {
return value;
}

public override string ToString() {
if (value > 0) return "DBBool.True";
if (value < 0) return "DBBool.False";
return "DBBool.Null";
}
}


12. Массивы

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

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

Каждое измерение массива имеет сопоставленную ему длину, выраженную в виде целого неотрицательного числа. Длины измерений не являются частью типа массива, они устанавливаются при создании экземпляра массива с конкретным типом во время выполнения. Длина измерения определяет допустимый диапазон индексов для этого измерения: для измерения с длиной N индексы могут находиться в диапазоне от 0 до N – 1 включительно. Общее число элементов в массиве является совокупностью длин всех измерений массива. Если одно или несколько измерений массива имеют нулевую длину, массив считается пустым.

Элемент массива может иметь любой тип, в том числе тип массива.

12.1 Типы массива

Тип массива записывается как тип_не_массива, за которым следуют спецификации_ранга:

тип_массива:
тип_не_массива спецификации_ранга

тип_не_массива:
тип

спецификации_ранга:
спецификация_ранга
спецификации_ранга спецификация_ранга

спецификация_ранга:
[ разделители_размерностей (н/о) ]

разделители_размерностей:
,
разделители_размерностей ,

Тип_не_массива является одним из типов, которые сами по себе не являются типом_массива.

Ранг типа массива задается левой спецификацией_ранга в типе_массива: спецификация_ранга указывает, что массив имеет ранг, равный единице плюс число меток «,» в этой спецификации.

Тип элемента массива представляет собой тип, полученный в результате удаления крайней левой спецификации_ранга:

· Тип массива в форме T[R] указывает массив с рангом R и типом элемента (типом_не_массива) T.

· Тип массива в форме T[R][R1]...[RN] указывает массив с рангом R и типом элемента T[R1]...[RN].

В результате данные спецификации_ранга считываются слева направо перед последним типом_не_массива элемента. Тип int[][,,][,] указывает одномерный массив трехмерных массивов из двумерных массивов значений с типом int.

Во время выполнения значение типа массива может быть равно null или может содержать ссылку на экземпляр массива этого типа.

System.Array

Во время выполнения…

IList

В одномерном массиве T[] реализуется интерфейс System.Collections.Generic.IList<T> (сокращенно IList<T>) и его базовые интерфейсы. В связи с этим выполняется неявное преобразование из T[] в IList<T> и его базовые интерфейсы. В дополнение к этому при наличии неявного преобразования ссылок из S в T в массиве S[] реализуется интерфейс IList<T> и выполняется неявное преобразование ссылок из S[] в IList<T> и его базовые интерфейсы (§6.1.6). При наличии явного преобразования ссылок из S в T выполняется явное преобразование ссылок из S[] в IList<T> и его базовые интерфейсы (§6.2.4). Например:

using System.Collections.Generic;

class Test
{
static void Main() {
string[] sa = new string[5];
object[] oa1 = new object[5];
object[] oa2 = sa;

IList<string> lst1 = sa; // ОК
IList<string> lst2 = oa1; // Ошибка, требуется приведение типов
IList<object> lst3 = sa; // ОК
IList<object> lst4 = oa1; // ОК

IList<string> lst5 = (IList<string>)oa1; // Исключение
IList<string> lst6 = (IList<string>)oa2; // ОК
}
}

Присваивание lst2 = oa1 приведет к ошибке компилирования, поскольку преобразование из object[] в IList<string> должно быть явным и не может выполняться неявно. Приведение типов в строке (IList<string>)oa1 приведет к созданию исключения во время выполнения, так как переменная oa1 ссылается на объект object[], а не на объект string[]. Тем не менее приведение типов (IList<string>)oa2 не приведет к созданию исключения, поскольку переменная oa2 ссылается на объект string[].

При выполнении явного или неявного преобразования ссылок из S[] в IList<T> также выполняется явное преобразование ссылок из интерфейса IList<T> и его базовых интерфейсов в S[] (§6.2.4).

Если в типе массива S[] реализуется интерфейс IList<T>, некоторые из членов реализованного интерфейса могут создавать исключения. Описание точного поведения этой реализации интерфейса выходит за рамки настоящей спецификации.

12.2 Создание массива

Экземпляры массива создаются при помощи выражений_создания_массива (§7.5.10.4) либо путем объявлений полей или локальных переменных, содержащих инициализатор_массива (§12.6).

Ранг массива и длина каждого из его измерений устанавливаются при создании экземпляра массива и остаются неизменными в течение всего времени жизни этого экземпляра. Другими словами, нельзя изменить ранг массива или длину его измерений.

Экземпляр массива всегда имеет тип массива. Тип System.Array является абстрактным типом, создание экземпляров непосредственно с этим типом невозможно.

Элементы массива, созданного с использованием выражений_создания_массива, всегда инициализируются значениями по умолчанию (§5.2).

12.3 Доступ к элементам массива

Доступ к элементам массива осуществляется при помощи выражений доступа_к_элементам (§7.5.6.1) в форме A[I1, I2, ..., IN], где A является выражением с типом массива, а каждый элемент IX — выражением с типом int, uint, long, ulong либо типом, который может быть неявно преобразован в один или несколько из этих типов. Результатом осуществления доступа к элементу массива является переменная, а именно элемент массива, выбранный по индексу.

Элементы массива могут перечисляться с использованием оператора foreach (§8.8.4).

12.4 Члены массива

В каждом типе массива наследуются члены, объявленные в классе System.Array.

12.5 Ковариация массивов

Если для любых двух переменных A и B ссылочного типа выполняется неявное (§6.1.6) или явное преобразование ссылок (§6.2.4) из A в B, такое же преобразование ссылок доступно из массива A[R] в массив B[R], где R указывает спецификацию_ранга (одинаковую для обоих массивов). Эта связь называется ковариацией массивов. Ковариация массивов в частности означает, что значение с типом массива A[R] фактически может быть ссылкой на экземпляр типа массива B[R] при условии, что доступно неявное преобразование ссылок из B в A.

В связи с существованием ковариации массива при присваивании элементам массива ссылочного типа выполняется проверка времени выполнения, которая гарантирует, что присваиваемое элементу массива значение имеет допустимый тип (§7.16.1). Например:

class Test
{
static void Fill(object[] array, int index, int count, object value) {
for (int i = index; i < index + count; i++) array[i] = value;
}

static void Main() {
string[] strings = new string[100];
Fill(strings, 0, 100, "Undefined");
Fill(strings, 0, 10, null);
Fill(strings, 90, 10, 0);
}
}

Присваивание массиву array[i] в методе Fill включает неявную проверку времени выполнения, гарантирующую, что объект, на который ссылается аргумент value, имеет значение null или является экземпляром типа, совместимого с фактическим типом элемента array. В методе Main первые два вызова метода Fill завершаются успешно, однако третий вызов этого метода приводит к возникновению исключения System.ArrayTypeMismatchException при выполнении первого присваивания массиву array[i]. Исключение возникает в связи с тем, что упакованное значение int не может храниться в массиве с типом string.

Ковариация массивов не распространяется на массивы с типами_по_значению. Например, преобразование из массива int[] в массив object[] невозможно.

12.6 Инициализаторы массива

Инициализаторы массива указываются в объявлениях полей (§10.5), объявлениях локальных переменных (§8.5.1) и выражениях создания массива (§7.5.10.4):

инициализатор_массива:
{ список_инициализаторов_переменных (н/о) }
{ список_инициализаторов_переменных , }

список_инициализаторов_переменных:
инициализатор_переменных
список_инициализаторов_переменных , инициализатор_переменных

инициализатор_переменных:
выражение
инициализатор_массива

Инициализатор массива состоит из последовательности переменных инициализаторов, заключенных в фигурные скобки «{» и «}» и разделенных запятыми «,». Каждый инициализатор переменной представляет собой выражение или (для многомерных массивов) инициализатор вложенного массива.

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

int[] a = {0, 2, 4, 6, 8};

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

int[] a = new int[] {0, 2, 4, 6, 8};

Инициализатор одномерного массива должен состоять из последовательности выражений, позволяющих выполнять присваивание для типа элементов массива. Эти выражения инициализируют элементы массива в порядке по возрастанию, начиная с элемента с нулевым индексом. Число выражений в инициализаторе массива определяет длину создаваемого экземпляра массива. Например, приведенный выше инициализатор массива создает экземпляр массива int[] с длиной 5, а затем инициализирует этот экземпляр следующими значениями:

a[0] = 0; a[1] = 2; a[2] = 4; a[3] = 6; a[4] = 8;

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

int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};

создает двумерный массив с длиной пять для левого измерения и с длиной два для правого измерения

int[,] b = new int[5, 2];

а затем инициализирует экземпляр массива следующими значениями:

b[0, 0] = 0; b[0, 1] = 1;
b[1, 0] = 2; b[1, 1] = 3;
b[2, 0] = 4; b[2, 1] = 5;
b[3, 0] = 6; b[3, 1] = 7;
b[4, 0] = 8; b[4, 1] = 9;

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

int i = 3;
int[] x = new int[3] {0, 1, 2}; // ОК
int[] y = new int[i] {0, 1, 2}; // Ошибка, i — не константа
int[] z = new int[3] {0, 1, 2, 3}; // Ошибка, несовпадение длины и инициализатора

В этом примере инициализатор для переменной y приведет к возникновению ошибки компилирования, поскольку выражение длины массива не является константой. Инициализатор для переменной z также приведет к возникновению ошибки компилирования, поскольку заданная длина и число элементов в инициализаторе не совпадают.


13. Интерфейсы

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

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

13.1 Объявления интерфейсов

Объявление_интерфейса является объявлением_типа (§9.6), где объявляется новый тип интерфейса.

объявление_интерфейса:
атрибуты (н/о) модификаторы_интерфейса (н/о) partial (н/о) interface идентификатор список_параметров_типа (н/о)
база_интерфейса (н/о) предложения_ограничений_параметров_типов (н/о) тело_интерфейса ;(н/о)

Объявление_интерфейса состоит из необязательного набора атрибутов (§17), за которым следуют необязательный набор модификаторов_интерфейса (§13.1.1), необязательный модификатор partial, ключевое слово interface и идентификатор, именующий интерфейс, необязательная спецификация списка_параметров_типа (§10.1.3), необязательная спецификация базы_интерфейса (§13.1.2), необязательная спецификация предложений_ограничений_параметров_типов (§10.1.5) и тело_интерфейса (§13.1.4), которое может завершаться точкой с запятой.

13.1.1 Модификаторы интерфейса

Объявление_интерфейса может включать последовательность модификаторов интерфейса:

модификаторы_интерфейса:
модификатор_интерфейса
модификаторы_интерфейса модификатор_интерфейса

модификатор_интерфейса:
new
public
protected
internal
private

Включение одного модификатора в объявление интерфейса несколько раз приведет к возникновению ошибки при компилировании.

Модификатор new разрешен только в том случае, если интерфейс определяется внутри класса. Он указывает, что в интерфейсе скрыт унаследованный член с таким же именем (см. §10.3.4).

Модфикаторы public, protected, internal и private управляют доступом к интерфейсу. Допустимые модификаторы определяются контекстом объявления интерфейса (§3.5.1).

Partial

13.1.3 Базовые интерфейсы

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

база_интерфейса::
: список_типов_интерфейсов

Для интерфейса сформированного типа явные базовые интерфейсы образуются путем замены объявлений явных базовых интерфейсов универсального типа для каждого параметра_типа в объявлении базового интерфейса соответствующими аргументами_типа сформированного типа.

Уровень доступа явных базовых интерфейсов для конкретного интерфейса должен быть не ниже уровня доступа самого интерфейса (§3.5.4). Например, указание интерфейса с уровнем доступа private или internal как базы_интерфейса для интерфейса с уровнем доступа public приведет к возникновению ошибки компилирования.

Явное или неявное наследование интерфейса из самого себя также приведет к возникновению ошибки компилирования.

Базовыми интерфейсами для интерфейса являются явные базовые интерфейсы и их базовые интерфейсы. Другими словами, набор базовых интерфейсов является полным транзитивным замыканием явных базовых интерфейсов, их явных базовых интерфейсов и так далее. Интерфейс наследует все члены своих базовых интерфейсов. В примере кода

interface IControl
{
void Paint();
}

interface ITextBox: IControl
{
void SetText(string text);
}

interface IListBox: IControl
{
void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

базовыми интерфейсами для интерфейса IComboBox являются интерфейсы IControl, ITextBox и IListBox.

Другими словами, интерфейс IComboBox наследует как члены SetText и SetItems, так и член Paint.

В классе или структуре, реализующих этот интерфейс, также неявно реализуются все базовые интерфейсы этого интерфейса.

13.1.4 Тело интерфейса

В теле_интерфейса определяются члены этого интерфейса.

тело_интерфейса:
{ объявления_членов_интерфейса (н/о) }

13.2 Члены интерфейса

К членам интерфейса относятся члены, унаследованные из базовых интерфейсов, а также члены, объявленные в самом интерфейсе.

объявления_членов_интерфейса:
объявление_членов_интерфейса
объявления_членов_интерфейса объявление_членов_интерфейса

объявление_членов_интерфейса:
объявление_метода_интерфейса
объявление_свойства_интерфейса
объявление_события_интерфейса
объявление_индексатора_интерфейса

В объявлении интерфейса может содержаться любое количество членов, в том числе ни одного. Члены интерфейса должны быть методами, свойствами, событиями или индексами. Интерфейс не может содержать константы, поля, операторы, конструкторы экземпляров, деструкторы и типы, а также статические члены любого вида.

Члены интерфейса неявно имеют уровень доступа «public». Включение модификаторов в объявления членов интерфейса приведет к возникновению ошибки компилирования. В частности, члены интерфейсов не могут объявляться с модификаторами abstract, public, protected, internal, private, virtual, override и static.

Например, в коде

public delegate void StringListEvent(IStringList sender);

public interface IStringList
{
void Add(string s);

int Count { get; }

event StringListEvent Changed;

string this[int index] { get; set; }
}

объявляется интерфейс, который содержит по одному из всех допустимых видов членов: метод, свойство, событие и индекс.

Объявление_интерфейса создает новую область объявления (§3.3), а объявления_членов_интерфейса, содержащиеся непосредственно в объявлении_интерфейса, вводят в эту область объявления новых членов. К объявлениям_членов_интерфейса применяются следующие правила:

· Имя метода должно отличаться от имен всех свойств и событий, объявленных в том же интерфейсе. Помимо этого подпись (§3.6) метода должна отличаться от подписей всех других методов, объявленных в том же интерфейсе, а два метода, объявленные в одном интерфейсе, не могут иметь подписи, отличающиеся только модификаторами ref и out.

· Имя свойства или события должно отличаться от имен всех остальных членов, объявленных в том же интерфейсе.

· Подпись индексатора должна отличаться от подписей всех остальных индексаторов, объявленных в том же интерфейсе.

Унаследованные члены интерфейса не относятся к области объявления этого интерфейса. Таким образом, в интерфейсе разрешается объявить член с именем или подписью унаследованного члена. В этом случае говорят, что член производного интерфейса скрывает члена базового интерфейса. Скрытие унаследованного члена не считается ошибкой, однако в этом случае компилятор выводит предупреждение. Чтобы не выводить такие предупреждения, объявление члена производного интерфейса должно содержать модификатор new, который указывает, что данный производный член должен скрывать базовый член. Эта тема более подробно рассматривается в §3.7.1.2.

Если модификатор new содержится в объявлении, где не выполняется скрытие унаследованного члена, выводится соответствующее предупреждение. Чтобы не выводить такие предупреждения, следует удалить модификатор new.

Обратите внимание, что члены в классе object не являются, строго говоря, членами какого-либо интерфейса (§13.2). Тем не менее, члены в классе object доступны при поиске членов интерфейса любого типа (§7.3.1).

13.2.1 Методы интерфейса

Методы интерфейса объявляются с использованием объявлений_методов_интерфейса:

объявление_метода_интерфейса:
атрибуты (н/о) new (н/о) тип возвращаемого значения идентификатор список_параметров_типа
( список_формальных_параметров (н/о) ) конструкции_ограничений_для_параметров_типа (н/о) ;

В объявлении метода интерфейса атрибуты, тип_возвращаемого_значения, идентификатор и список_формальных_параметров имеют такой же смысл, что и в объявлении метода класса (§10.6). В объявлении метода интерфейса не разрешается указывать тело метода, в связи с этим объявление всегда завершается точкой с запятой.

13.2.2 Свойства интерфейса

Свойства интерфейса объявляются с использованием объявлений_свойств_интерфейса:

объявление_свойства_интерфейса:
атрибуты (н/о) new (н/о) тип идентификатор { методы_доступа_к_интерфейсу }

методы_доступа_к_интерфейсу:
атрибуты (н/о) get ;
атрибуты (н/о) set ;
атрибуты (н/о) get ; атрибуты (н/о) set ;
атрибуты (н/о) set ; атрибуты (н/о) get ;

В объявлении свойства интерфейса атрибуты и идентификатор имеют такой же смысл, что и в объявлении свойства класса (§10.7).

Методы доступа в объявлении свойства интерфейса соответствуют методам доступа в объявлении свойства класса (§10.7.2) за тем исключением, что тело метода доступа должно всегда быть точкой с запятой. Таким образом, методы доступа просто указывают, каким является свойство: предназначенным для чтения и записи, только для чтения или только для записи.

13.2.3 События интерфейса

События интерфейса объявляются с использованием объявлений_событий_интерфейса:

объявление_события_интерфейса:
атрибуты (н/о) new (н/о) event тип...идентификатор ;

В объявлении события интерфейса атрибуты и идентификатор имеют такой же смысл, что и в объявлении события класса (§10.8).

13.2.4 Индексаторы интерфейса

Индексаторы интерфейса объявляются с использованием объявлений_индексаторов_интерфейса:

объявление_индексатора_интерфейса:
атрибуты (н/о) new (н/о) тип this [ список_формальных_параметров ] { методы_доступа_к_интерфейсу }

В объявлении индексатора интерфейса атрибуты, тип и список_формальных_параметров имеют такой же смысл, что и в объявлении индексатора класса (§10.9).

Методы доступа в объявлении индексатора интерфейса соответствуют методам доступа в объявлении индексатора класса (§10.9) за тем исключением, что тело метода доступа должно всегда быть точкой с запятой. Таким образом, методы доступа просто указывают, каким является индекс: предназначенным для чтения и записи, только для чтения или только для записи.

13.2.5 Доступ к членам интерфейса

Доступ к членам интерфейса осуществляется с использованием выражений доступа к членам (§7.5.4) и доступа к индексаторам (§7.5.6.2) в форме I.M и I[A], где I указывает тип интерфейса, M — метод, свойство или событие этого типа интерфейса, а A — список аргументов индексатора.

Для интерфейсов, которые наследуют строго от одного предка (каждый интерфейс в цепочке наследования имеет только один прямой базовый интерфейс или не имеет его вовсе), результаты применения правил поиска членов (§7.3), вызова методов (§7.5.6.2) и доступа к индексатору (§7.5.5.1) аналогичны результатам для классов и структур: производные члены скрывают соответствующие базовые члены с тем же именем или той же подписью. Однако в интерфейсах с множественным наследованием могут происходить неоднозначности, связанные с объявлением в двух (или более) базовых интерфейсах методов с одинаковыми именами или подписями. В данном разделе приводится несколько примеров подобных ситуаций. Во всех случаях для разрешения таких неоднозначностей можно использовать явное приведение типов.

В примере кода

interface IList
{
int Count { get; set; }
}

interface ICounter
{
void Count(int i);
}

interface IListCounter: IList, ICounter {}

class C
{
void Test(IListCounter x) {
x.Count(1); //Ошибка
x.Count = 1; //Ошибка
((IList)x).Count = 1; // ОК, вызывается метод IList.Count.set
((ICounter)x).Count(1); // ОК, вызывается метод ICounter.Count
}
}

выполнение первых двух операторов ведет к ошибкам при компилировании, поскольку поиск членов (§7.3) метода Count в интерфейсе IListCounter является неоднозначным. Как демонстрируется в примере, такая неоднозначность разрешается посредством приведения типа x к типу соответствующего базового интерфейса. Такое приведение не требует дополнительных затрат ресурсов во время выполнения — оно просто означает, что при компилировании экземпляр будет рассматриваться как находящийся на предыдущем уровне наследования.

В примере кода

interface IInteger
{
void Add(int i);
}

interface IDouble
{
void Add(double d);
}

interface INumber: IInteger, IDouble {}

class C
{
void Test(INumber n) {
n.Add(1); // Вызывает метод IInteger.Add
n.Add(1.0); // Применим только метод IDouble.Add
((IInteger)n).Add(1); // Кандидатом является только метод IInteger.Add
((IDouble)n).Add(1); // Кандидатом является только метод IDouble.Add
}
}

при вызове метода n.Add(1) выбирается метод IInteger.Add в результате применения правил разрешения перегрузки, рассматриваемых в §7.4.3. Точно так же при вызове метода n.Add(1.0) выбирается метод IDouble.Add. Если вставлено явное приведение типов, кандидатом является только один метод, что позволяет избежать неоднозначности.

В примере кода

interface IBase
{
void F(int i);
}

interface ILeft: IBase
{
new void F(int i);
}

interface IRight: IBase
{
void G();
}

interface IDerived: ILeft, IRight {}

class A
{
void Test(IDerived d) {
d.F(1); // Вызывает ILeft.F
((IBase)d).F(1); // Вызывает IBase.F
((ILeft)d).F(1); // Вызывает ILeft.F
((IRight)d).F(1); // Вызывает IBase.F
}
}

член IBase.F скрыт членом ILeft.F. В результате при вызове метода d.F(1) выбирается метод ILeft.F несмотря на то, что метод IBase.F не скрыт явным образом в пути доступа, идущем через интерфейс IRight.

Интуитивное правило скрытия членов в интерфейсах с множественным наследованием звучит так: если член скрыт в одном из путей доступа, он скрыт и во всех остальных путях доступа. Поскольку в пути доступа от IDerived через ILeft к IBase метод IBase.F скрыт, этот член также является скрытым в пути доступа от IDerived через IRight до IBase.

13.3 Полные имена членов интерфейса

Иногда ссылка на член интерфейса имеет вид полного имени. Полное имя члена интерфейса состоит из имени интерфейса, в котором объявлен данный член, за которым следуют точка и имя нужного члена. Полное имя члена указывает на интерфейс, в котором объявлен этот член. Например, в объявлениях

interface IControl
{
void Paint();
}

interface ITextBox: IControl
{
void SetText(string text);
}

полным именем члена Paint является имя IControl.Paint, а полным именем члена SetText — имя ITextBox.SetText.

В приведенном выше примере нельзя указать член Paint как ITextBox.Paint.

Если интерфейс является частью пространства имен, полное имя члена интерфейса включает имя этого пространства имен. Например:

namespace System
{
public interface ICloneable
{
object Clone();
}
}

Здесь полным именем метода Clone является имя System.ICloneable.Clone.

13.4 Реализации интерфейсов

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

interface ICloneable
{
object Clone();
}

interface IComparable
{
int CompareTo(object other);
}

class ListEntry: ICloneable, IComparable
{
public object Clone() {...}

public int CompareTo(object other) {...}
}

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

interface IControl
{
void Paint();
}

interface ITextBox: IControl
{
void SetText(string text);
}

class TextBox: ITextBox
{
public void Paint() {...}

public void SetText(string text) {...}
}

Здесь класс TextBox реализует как класс IControl, так и класс ITextBox.

Базовые интерфейсы, указанные в объявлении класса, могут относиться к интерфейсам сформированного типа (§4.4). Базовый интерфейс не может быть параметром типа сам по себе, хотя он может включать параметры типа, находящиеся в соответствующей области. В следующем коде демонстрируется реализация класса и расширение сформированных типов:

class C<U,V> {}

interface I1<V> {}

class D: C<string,int>, I1<string> {}

class E<T>: C<int,T>, I1<T> {}

Базовые интерфейсы объявления универсального класса должны удовлетворять правилу уникальности, которое изложено в §13.4.2.

13.4.1 Явные реализации членов интерфейса

Для целей реализации интерфейсов в классе или структуре могут объявляться явные реализации членов интерфейса. Явная реализация члена интерфейса представляет собой объявление метода, свойства, события или индекса, содержащее ссылку на полное имя члена интерфейса. Например:

interface IList<T>
{
T[] GetElements();
}

interface IDictionary<K,V>
{
V this[K key];

void Add(K key, V value);
}

class List<T>: IList<T>, IDictionary<int,T>
{
T[] IList<T>.GetElements() {...}

T IDictionary<int,T>.this[int index] {...}

void IDictionary<int,T>.Add(int index, T value) {...}
}

В этом примере строки IDictionary<int,T>.this и IDictionary<int,T>.Add являются явными реализациями членов интерфейса.

В некоторых случаях имя члена интерфейса может быть неподходящим для реализующего класса. В этом случае член интерфейса может быть реализован с использованием явной реализации члена интерфейса. Например, в классе, который реализует абстракцию файла, скорее всего будет реализована функция-член Close, в результате выполнения этой функции высвобождается файловый ресурс и реализуется метод Dispose интерфейса IDisposable с использованием явной реализации члена интерфейса:

interface IDisposable
{
void Dispose();
}

class MyFile: IDisposable
{
void IDisposable.Dispose() {
Close();
}

public void Close() {
// Выполняется все необходимое для закрытия файла
System.GC.SuppressFinalize(this);
}
}

Получить доступ к явной реализации члена интерфейса с использованием его полного имени в вызванном методе, обращении к свойству или индексатору невозможно. Доступ к явной реализации члена интерфейса можно получить только через экземпляр интерфейса, в этом случае обращение к нему следует выполнять просто по имени его члена.

Включение в явную реализацию члена интерфейса модификаторов доступа, а также модификаторов abstract, virtual, override или static, приведет к ошибке при компилировании.

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

Явные реализации члена интерфейса служат двум основным целям:

· Поскольку к явным реализациям члена интерфейса нельзя получить доступ через экземпляры классов или структур, они позволяют исключить реализации интерфейса из открытого интерфейса класса или структуры. Это особенно полезно в том случае, если в классе или структуре реализуется внутренний интерфейс, который не предоставляет интереса для объекта, где используется этот класс или эта структура .

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

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

class Shape: ICloneable
{
object ICloneable.Clone() {...}

int IComparable.CompareTo(object other) {...} // Недопустимо
}

объявление метода IComparable.CompareTo приведет к возникновению ошибки при компилировании, поскольку интерфейс IComparable не включен в список базовых классов класса Shape и не является базовым интерфейсом для интерфейса ICloneable. Аналогичным образом в объявлениях

class Shape: ICloneable
{
object ICloneable.Clone() {...}
}

class Ellipse: Shape
{
object ICloneable.Clone() {...} // недопустимо
}

объявление метода ICloneable.Clone в классе Ellipse приведет к ошибке при компилировании, поскольку интерфейс ICloneable не указан явно в списке базовых классов класса Ellipse.

Полное имя интерфейса должно указывать интерфейс, в котором был объявлен этот член. Так, в объявлениях

interface IControl
{
void Paint();
}

interface ITextBox: IControl
{
void SetText(string text);
}

class TextBox: ITextBox
{
void IControl.Paint() {...}

void ITextBox.SetText(string text) {...}
}

явная реализация члена интерфейса Paint должна быть записана в форме IControl.Paint.

13.4.2 Уникальность реализованных интерфейсов

Интерфейсы, реализованные в объявлении уникального типа, должны оставаться уникальными для всех сформированных типов. Если бы это правило не действовало, было бы невозможно определить правильный метод для вызова в конкретных сформированных типах. Например, предположим, что объявление универсального класса может иметь следующий вид:

interface I<T>
{
void F();
}

class X<U,V>: I<U>, I<V> //Ошибка: интерфейсы I<U> и I<V> конфликтуют друг с другом
{
void I<U>.F() {...}
void I<V>.F() {...}
}

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

I<int> x = new X<int,int>();
x.F();

Чтобы определить, является ли список объявлений универсального типа допустимым, требуется выполнить следующие шаги:

· L — список интерфейсов, непосредственно определенных в универсальном объявлении класса, структуры или интерфейса C.

· Добавить в L все базовые интерфейсы для интерфейсов, уже содержащихся в L.

· Удалить из L все дубликаты

· Если после создания на основе C любого возможного типа в результате подстановки в L аргументов типа два интерфейса в L станут идентичными, такое объявление C является недопустимым. При определении всех возможных сформированных типов не учитываются объявления ограничений.

В приведенном выше объявлении класса X список интерфейсов L состоит из интерфейсов I<U> и I<V>. Объявление является недопустимым, поскольку в любом сформированном типе, где U и V имеют одинаковый тип, эти два интерфейса также будут иметь одинаковый тип.

Можно объединять интерфейсы, находящиеся на разных уровнях наследования:

interface I<T>
{
void F();
}

class Base<U>: I<U>
{
void I<U>.F() {…}
}

class Derived<U,V>: Base<U>, I<V> // ОК
void I<V>.F() {…}
}

Этот код является допустимым, несмотря на то, что в классе Derived<U,V> реализуются как интерфейс I<U>, так и интерфейс I<V>. Код

I<int> x = new Derived<int,int>();
x.F();

вызовет метод класса Derived, поскольку класс Derived<int,int> фактически выполняет I<int> (§13.4.6).

13.4.3 Реализация универсальных методов

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

Тем не менее, если в универсальном методе явно реализуется метод интерфейса, ограничения для реализующего метода не допускаются. Эти ограничения наследуются из метода интерфейса

interface I<A,B,C>
{
void F<T>(T t) where T: A;
void G<T>(T t) where T: B;
void H<T>(T t) where T: C;
}

class C: I<object,C,string>
{
public void F<T>(T t) {...} // ОК
public void G<T>(T t) where T: C {...} // ОК
public void H<T>(T t) where T: string {...} // Ошибка
}

Метод C.F<T> неявно реализует метод I<object,C,string>.F<T>. В этом случае в методе C.F<T> не требуется (и не допускается) указывать ограничение T: object, так как object является неявным ограничением для всех параметров типа. Метод C.G<T> неявно реализует метод I<object,C,string>.G<T>, поскольку ограничения совпадают с установленными в интерфейсе после замены параметров типа интерфейса соответствующими аргументами типа. Ограничение для метода C.H<T> является ошибочным, так как запечатанные типы ( в данном случае string) не могут использоваться как ограничения. Отсутствие ограничения также было бы ошибкой, поскольку ограничения в неявных реализациях метода интерфейса должны совпадать. Таким образом, выполнить неявную реализацию метода I<object,C,string>.H<T> невозможно. Этот метод интерфейса может быть реализован только с использованием явной реализации члена интерфейса:

class C: I<object,C,string>
{
...

public void H<U>(U u) where U: class {...}

void I<object,C,string>.H<T>(T t) {
string s = t; // ОК
H<T>(t);
}
}

В этом примере явная реализация члена интерфейса вызывает открытый метод с применением более мягких ограничений. Обратите внимание, что присваивание из t в s является допустимым, поскольку T наследует ограничение T: string, хотя это ограничение и не указано в исходном коде.

13.4.4 Сопоставление интерфейсов

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

При сопоставлении интерфейсов для класса или структуры C выявляется реализация каждого из интерфейсов, указанных в списке базовых классов C. Реализация конкретного члена интерфейса I.M, где I указывает интерфейс, в котором объявлен член M, определяется путем проверки каждого класса или структуры S, начиная с C и последовательно проходя по каждому базовому классу C до выявления совпадения:

· Если S содержит объявление явной реализации члена интерфейса, которая соответствует I и M, этот член является реализацией члена I.M.

· В обратном случае, если S содержит объявление открытого члена, не являющегося статическим, который соответствует M, то реализацией члена I.M является этот член. При выявлении совпадения для нескольких членов не указывается, какой из них является реализацией члена I.M. Эта ситуация может произойти только в том случае, если S относится к сформированному типу, в котором два члена, согласно объявлению в универсальном типе, имеют разные подписи, однако аргументы типа делают их подписи идентичными.

Если не удается выявить реализации для всех членов всех интерфейсов, указанных в списке базовых классов C, возникает ошибка при компилировании. Обратите внимание, что члены интерфейса содержат члены, наследуемые из базовых интерфейсов.

При сопоставлении интерфейсов член класса A считается соответствующим члену класса B в указанных ниже случаях.

· A и B являются методами, у A и B совпадают имена, типы и списки формальных параметров.

· A и B являются свойствами, у A и B совпадают имена и типы, A имеет те же методы доступа, что и B (A может иметь дополнительные методы доступа, если этот член не относится к явным реализациям члена интерфейса).

· A и B являются событиями, у A и B совпадают имена и типы.

· A и B являются индексами, у A и B совпадают типы и списки формальных параметров, A имеет те же методы доступа, что и B (A может иметь дополнительные методы доступа, если этот член не относится к явным реализациям члена интерфейса).

Алгоритм сопоставления интерфейсов предполагает следующее:

· При определении члена класса или структуры, реализующего член интерфейса, явные реализации члена интерфейса имеют приоритет по сравнению с другими членами того же класса или структуры.

· В сопоставлении интерфейсов не участвуют члены, которые являются статическими или не относятся к открытым.

В примере кода

interface ICloneable
{
object Clone();
}

class C: ICloneable
{
object ICloneable.Clone() {...}

public object Clone() {...}
}

реализацией метода Clone интерфейса ICloneable становится член ICloneable.Clone класса C, поскольку явные реализации имеют приоритет по отношению к другим членам.

Если в классе или структуре реализуется два или более интерфейса, содержащих член с одинаковым именем, типом и типами параметров, можно сопоставить каждый из таких членов интерфейса единственному члену класса или структуры. Например:

interface IControl
{
void Paint();
}

interface IForm
{
void Paint();
}

class Page: IControl, IForm
{
public void Paint() {...}
}

В этом примере методу Paint класса Page сопоставляются методы Paint как класса IControl, так и класса IForm. Разумеется, для каждого из этих двух методов можно создать отдельную явную реализацию члена интерфейса.

Если в классе или структуре реализуется интерфейс, который содержит скрытые члены, некоторые члены необходимо реализовать с использованием явных реализаций члена интерфейса. Например:

interface IBase
{
int P { get; }
}

interface IDerived: IBase
{
new int P();
}

Для реализации этого интерфейса потребуется по меньшей мере одна явная реализация члена интерфейса в одной из приведенных ниже форм

class C: IDerived
{
int IBase.P { get {...} }

int IDerived.P() {...}
}

class C: IDerived
{
public int P { get {...} }

int IDerived.P() {...}
}

class C: IDerived
{
int IBase.P { get {...} }

public int P() {...}
}

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

interface IControl
{
void Paint();
}

interface ITextBox: IControl
{
void SetText(string text);
}

interface IListBox: IControl
{
void SetItems(string[] items);
}

class ComboBox: IControl, ITextBox, IListBox
{
void IControl.Paint() {...}

void ITextBox.SetText(string text) {...}

void IListBox.SetItems(string[] items) {...}
}

невозможно иметь отдельные реализации интерфейса IControl, включенного в список базовых классов, интерфейса IControl, наследуемого интерфейсом ITextBox, и интерфейса IControl, который наследуется интерфейсом IListBox. Для этих интерфейсов отсутствует указание на их различие. Наоборот, в реализациях интерфейсов ITextBox и IListBox используется одна и та же реализация интерфейса IControl, а класс ComboBox просто считается реализующим три интерфейса: IControl, ITextBox и IListBox.

Члены базового класса участвуют в сопоставлении интерфейсов. В примере кода

interface Interface1
{
void F();
}

class Class1
{
public void F() {}

public void G() {}
}

class Class2: Class1, Interface1
{
new public void G() {}
}

метод F в классе Class1 используется в реализации интерфейса Interface1 в классе Class2.

13.4.5 Наследование реализаций интерфейсов

Класс наследует все реализации интерфейсов, содержащиеся в его базовых классах.

Без явной повторной реализации интерфейса, в производном классе нельзя изменить сопоставление интерфейсов, унаследованных им из базовых классов. Например, в объявлениях

interface IControl
{
void Paint();
}

class Control: IControl
{
public void Paint() {...}
}

class TextBox: Control
{
new public void Paint() {...}
}

метод Paint классе TextBox скрывает метод Paint в классе Control, однако не меняет сопоставление Control.Paint с IControl.Paint, вызов же метода Paint в экземплярах класса и экземплярах интерфейса приведет к следующим результатам

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint(); // вызывает метод Control.Paint();
t.Paint(); // вызывает метод TextBox.Paint();
ic.Paint(); // вызывает метод Control.Paint();
it.Paint(); // вызывает метод Control.Paint();

Тем не менее, при сопоставлении метода интерфейса с виртуальным методом в классе можно переопределить этот виртуальный метод в производных классах и изменить реализацию интерфейса. Например, изменение записи приведенных выше объявлений на

interface IControl
{
void Paint();
}

class Control: IControl
{
public virtual void Paint() {...}
}

class TextBox: Control
{
public override void Paint() {...}
}

приведет к следующим результатам

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint(); // вызывает метод Control.Paint();
t.Paint(); // вызывает метод TextBox.Paint();
ic.Paint(); // вызывает метод Control.Paint();
it.Paint(); // вызывает метод TextBox.Paint();

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

interface IControl
{
void Paint();
}

class Control: IControl
{
void IControl.Paint() { PaintControl(); }

protected virtual void PaintControl() {...}
}

class TextBox: Control
{
protected override void PaintControl() {...}
}

В этом примере в классах, являющихся производными от класса Control, можно указать реализацию метода IControl.Paint путем переопределения метода PaintControl.

13.4.6 Повторная реализация интерфейса

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

Для повторной реализации интерфейса действуют такие же правила сопоставления интерфейсов, что и для первой реализации интерфейса. Таким образом, унаследованное сопоставление интерфейсов не влияет на сопоставление интерфейсов, установленное в повторной реализации этого интерфейса. Например, в объявлениях

interface IControl
{
void Paint();
}

class Control: IControl
{
void IControl.Paint() {...}
}

class MyControl: Control, IControl
{
public void Paint() {}
}

тот факт, что в классе Control метод IControl.Paint сопоставлен методу Control.IControl.Paint, не влияет на повторную реализацию в классе MyControl, где метод IControl.Paint сопоставлен методу MyControl.Paint.

В процедуре сопоставления повторно реализованных интерфейсов участвуют унаследованные объявления открытых членов и унаследованные явные объявления членов интерфейса. Например:

interface IMethods
{
void F();
void G();
void H();
void I();
}

class Base: IMethods
{
void IMethods.F() {}
void IMethods.G() {}
public void H() {}
public void I() {}
}

class Derived: Base, IMethods
{
public void F() {}
void IMethods.H() {}
}

В этом примере реализация интерфейса IMethods в классе Derived сопоставляет методы этого интерфейса методам Derived.F, Base.IMethods.G, Derived.IMethods.H и Base.I.

При реализации интерфейса в классе также неявно реализуются все базовые интерфейсы этого интерфейса. Аналогичным образом повторная реализация интерфейса неявно является повторной реализацией всех базовых интерфейсов этого интерфейса. Например:

interface IBase
{
void F();
}

interface IDerived: IBase
{
void G();
}

class C: IDerived
{
void IBase.F() {...}

void IDerived.G() {...}
}

class D: C, IDerived
{
public void F() {...}

public void G() {...}
}

Здесь в повторной реализации интерфейса IDerived также повторно реализуется интерфейс IBase, в результате чего метод IBase.F сопоставлен методу D.F.

13.4.7 Абстрактные классы и интерфейсы

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

interface IMethods
{
void F();
void G();
}

abstract class C: IMethods
{
public abstract void F();
public abstract void G();
}

В этом примере реализация интерфейса IMethods сопоставляет методы F и G абстрактным методам, которые должны быть переопределены в производных от C классах, не являющихся абстрактными.

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

interface IMethods
{
void F();
void G();
}

abstract class C: IMethods
{
void IMethods.F() { FF(); }

void IMethods.G() { GG(); }

protected abstract void FF();

protected abstract void GG();
}

В этом примере в производных от C классах, не являющихся абстрактными, необходимо переопределить методы FF и GG, что обеспечит фактическую реализацию интерфейса IMethods.


14. Перечисляемые типы

Перечисляемый тип является особым типом значения (§4.1), который объявляет набор именованных констант.

Пример:

enum Color
{
Red,
Green,
Blue
}

Здесь объявляется перечисляемый тип с именем Color, содержащий члены Red, Green и Blue.

14.1 Объявления перечислений

Объявление перечисления объявляет новый перечисляемый тип. Объявление перечисления начинается с зарезервированного слова enum и содержит определение имени, доступности, базового типа и членов перечисления.

объявление_перечисления:
атрибутынеоб модификаторы_перечислениянеоб enum идентификатор база_перечислениянеоб тело_перечисления ;необ

база_перечисления:
: целый_тип

тело_перечисления:
{ объявления_члена_перечислениянеоб }
{ объявления_члена_перечисления , }

Каждый тип перечисления имеет соответствующий целый тип, называемый базовым типом типа перечисления. Этот базовый тип должен иметь возможность представлять все значения перечислителя, определенные в перечислении. Объявление перечисления может явно объявлять базовый тип byte, sbyte, short, ushort, int, uint, long или ulong. Обратите внимание, что тип char не может использоваться в качестве базового типа. Объявление перечисления, которое не содержит явное объявления базового типа, имеет базовый тип int.

Пример:

enum Color: long
{
Red,
Green,
Blue
}

Здесь объявляется перечисление с базовым типом long. Разработчик может выбрать использование базового типа long, как это показано в примере, чтобы иметь возможность использования значений, которые находятся в диапазоне типа long, но не входят в диапазон типа int, или чтобы сохранить этот выбор на будущее.

14.2 Модификаторы перечисления

Объявление_перечисления может, при необходимости, включать последовательность модификаторов перечисления:

модификаторы_перечисления:
модификатор_перечисления
модификаторы_перечисления модификатор_перечисления

модификатор_перечисления:
new
public
protected
internal
private

Возникает ошибка времени компиляции, если один и тот же модификатор встречается несколько раз в объявлении перечисления.

Модификаторы объявления перечисления имеют тот же смысл, что и модификаторы объявления класса (§10.1.1). Однако обратите внимание, что в объявлении перечисления не разрешается использовать модификаторы abstract и sealed. Перечисления не могут быть абстрактными и не допускают производные типы.

14.3 Члены перечисления

Тело объявления перечисляемого типа определяет нуль или более членов перечисления, которые являются именованными константами перечисляемого типа. Два члена не могут иметь одинаковое имя.

объявления_членов_перечисления:
объявление_члена_перечисления
объявления_членов_перечисления , объявление_члена_перечисления

объявление_члена_перечисления:
атрибутынеоб идентификатор
атрибутынеоб идентификатор = константное_выражение

Каждый член перечисления имеет связанное с ним постоянное значение. Тип этого значения является базовым типом для содержащего его перечисления. Постоянное значение для каждого члена перечисления должно находиться в диапазоне базового типа для перечисления. Пример:

enum Color: uint
{
Red = -1,
Green = -2,
Blue = -3
}

приводит к ошибке времени компиляции, так как значения констант -1, -2 и -3 не находятся в диапазоне базового целого типа uint.

Несколько членов перечисления могут совместно использовать одно и то же связанное значение. Например:

enum Color
{
Red,
Green,
Blue,

Max = Blue
}

Здесь показано перечисление, в котором два его члена — Blue и Max — имеют одно и то же связанное значение.

Связанное значение члена перечисления присваивается явно или неявно. Если объявление члена перечисления имеет инициализатор константного_выражения, значение этого константного выражения, неявно преобразованное в базовый тип перечисления, является связанным значением члена перечисления. Если в объявлении члена перечисления нет инициализатора, его связанное значение устанавливается неявно следующим образом:

· если член перечисления является первым членом перечисления, объявленным в перечисляемом типе, его связанное значение равно нулю;

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

В примере:

using System;

enum Color
{
Red,
Green = 10,
Blue
}

class Test
{
static void Main() {
Console.WriteLine(StringFromColor(Color.Red));
Console.WriteLine(StringFromColor(Color.Green));
Console.WriteLine(StringFromColor(Color.Blue));
}

static string StringFromColor(Color c) {
switch (c) {
case Color.Red:
return String.Format("Red = {0}", (int) c);

case Color.Green:
return String.Format("Green = {0}", (int) c);

case Color.Blue:
return String.Format("Blue = {0}", (int) c);

default:
return "Недопустимый цвет";
}
}
}

выводятся на печать имена членов перечисления и их связанные значения. В результате имеем:

Red = 0
Green = 10
Blue = 11

по следующим причинам:

· члену перечисления Red автоматически присваивается значение нуль (так как у него нет инициализатора и он является первым членом перечисления);

· члену перечисления Green явно присваивается значение 10;

· а члену перечисления Blue автоматически присваивается значение на единицу большее, чем у члена, предшествующего ему в тексте.

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

Пример:

enum Circular
{
A = B,
B
}

приводит к ошибке времени компиляции, так как объявления A и B являются циклическими. Член A зависит от B явно, а член B зависит от A неявно.

Члены перечисления именуются и имеют область видимости совершенно аналогично полям в классах. Областью видимости члена перечисления является тело содержащего его перечисляемого типа. В этой области видимости на члены перечисления можно ссылаться по их простому имени. Из всего остального кода имя члена перечисления должно уточняться именем его перечисляемого типа. У членов перечисления нет какой-либо объявленной доступности — член перечисления доступен, если доступен содержащий его перечисляемый тип.

System.Enum

Обратите внимание, что…

14.5 Значения перечисления и операции

Каждый перечисляемый тип определяет отдельный тип; требуется явное преобразование перечисления (§6.2.2), чтобы выполнить преобразование между перечисляемым типом и целым типом, или между двумя перечисляемыми типами. Набор значений, которые может принимать перечисляемый тип, не ограничен его членами перечисления. А именно, любое значение базового типа перечисления может быть приведено к перечисляемому типу, и оно является отдельным допустимым значением этого перечисляемого типа.

Члены перечисления имеют тип содержащего их перечисляемого типа (кроме случаев, когда они входят в инициализаторы других членов перечисления: см. §14.3). Значением члена перечисления, объявленного в перечисляемом типе E со связанным значением v, является (E)v.

Со значениями перечисляемых типов могут использоваться следующие операторы: ==, !=, <, >, <=, >= (§7.9.5), бинарный + (§7.7.4), бинарный ‑ (§7.7.5), ^, &, | (§7.10.2), ~ (§7.6.4), ++, -- (§7.5.9 и §7.6.5) и sizeof (§18.5.4).

Каждый перечисляемый тип автоматически производится от класса System.Enum (который в свою очередь производится от System.ValueType и object). Таким образом, унаследованные методы и свойства этого класса могут использоваться в значениях перечисляемого типа.


15. Делегаты

Делегаты делают возможными сценарии, к которым другие языки — например, C++, Pascal и Modula — обращаются с помощью указателей на функции. Однако в отличие от указателей на функции языка C++, делегаты являются полностью объектно-ориентированными, и в отличие от указателей на функции-члены языка C++, делегаты инкапсулируют и экземпляр объекта, и метод.

Объявление делегата определяет класс, производный от класса System.Delegate. Экземпляр делегата инкапсулирует список вызова, являющийся списком из одного или более методов, на каждый из которых ссылаются как на объект, допускающий вызов. Для методов экземпляров объект, допускающий вызов, состоит из экземпляра и метода для этого экземпляра. Для статических методов объект, допускающий вызов, состоит только из метода. Вызов экземпляра делегата с соответствующим набором аргументов приводит к вызову каждого из допускающих вызов объектов делегата с данным набором аргументов.

Интересным и полезным свойством экземпляра делегата является то, что он не знает и не заботится о классах методов, которые инкапсулирует; имеет значение только совместимость этих методов (§15.1) с типом делегата. Это делает делегаты вполне пригодными для «анонимного» вызова.

15.1 Объявления делегатов

Объявление_делегата — это объявление_типа (§9.6), которое объявляет новый тип делегата.

объявление_делегата:
атрибутынеоб модификаторы_делегатанеоб delegate тип_возвращаемого_значения идентификатор список_параметров_типанеоб
( список_формальных_параметровнеоб ) предложения_ограничений_параметров_типанеоб ;

модификаторы_делегата:
модификатор_делегата
модификаторы_делегата модификатор_делегата

модификатор_делегата:
new
public
protected
internal
private

Возникает ошибка времени компиляции, если один и тот же модификатор встречается несколько раз в объявлении делегата.

Модификатор new допускается только для делегатов, объявленных в другом типе; в этом случае модификатор указывает, что такой делегат скрывает унаследованный член с тем же именем, как описано в §10.3.4.

Модификаторы public, protected, internal и private управляют доступностью типа делегата. В зависимости от контекста, в котором встречается объявление делегата, некоторые из этих модификаторов запрещены (§3.5.1).

Именем типа делегата является идентификатор.

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

Необязательный список_параметров_типа задает параметры типа для самого делегата.

Типы делегатов в C# являются эквивалентом имени, а не структурным эквивалентом. А именно, два разных типа делегатов с одинаковыми списками параметров и типом возвращаемого значения считаются разными типами делегатов. Однако экземпляры двух отдельных, но структурно эквивалентных типов делегатов могут сопоставляться как равные (§7.9.8).

Например:

delegate int D1(int i, double d);

class A
{
public static int M1(int a, double b) {...}
}

class B
{
delegate int D2(int c, double d);

public static int M1(int f, double g) {...}

public static void M2(int k, double l) {...}

public static int M3(int g) {...}

public static void M4(int g) {...}
}

Типы делегатов D1 и D2 оба совместимы с методами A.M1 и B.M1, так как у них одинаковый тип возвращаемого значения и список параметров; однако эти типы делегатов являются двумя разными типами, так что они не являются взаимозаменяемыми. Типы делегатов D1 и D2 несовместимы с методами B.M2, B.M3 и B.M4, так как у них разные типы возвращаемого значения или списки параметров.

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

delegate bool Predicate<T>(T value);

class X
{
static bool F(int i) {...}

static bool G(string s) {...}
}

Тип делегата Predicate<int> совместим с методом X.F, а тип делегата Predicate<string> совместим с методом X.G.

Объявление_делегата — это единственный способ объявить тип делегата. Тип делегата является типом класса, производного от System.Delegate. Типы делегатов неявно sealed, поэтому никакие производные типы от типа делегата не допускаются. Недопустимо также производить тип класса не делегата от System.Delegate. Обратите внимание, что System.Delegate сам не является типом делегата; это тип класса, от которого производятся все типы делегатов.

Язык C# предоставляет специальный синтаксис для создания экземпляра и вызова делегата. За исключением создания экземпляра, любые операции, которые могут применяться к классу или экземпляру класса, могут также применяться к классу делегата или экземпляру делегата, соответственно. В частности, можно получить доступ к членам типа System.Delegate с помощью обычного синтаксиса доступа к члену.

Набор методов, инкапсулированных экземпляром делегата, называется списком вызова. Если экземпляр делегата создается (§15.2) из единственного метода, он инкапсулирует этот метод, и его список вызова содержит только одну запись. Однако, когда объединяются два непустых экземпляра делегата, их списки вызова связываются — в очередности: левый операнд, а затем правый операнд — для формирования нового списка вызова, содержащего две или более записей.

Делегаты объединяются с помощью бинарных операторов + (§7.7.4) и += (§7.16.2). Делегат можно удалить из объединения делегатов с помощью бинарных операторов - (§7.7.5) и -= (§7.16.2). Делегаты можно проверять на равенство (§7.9.8).

В следующем примере показано создание нескольких экземпляров делегата и их соответствующие списки вызова:

delegate void D(int x);

class C
{
public static void M1(int i) {...}

public static void M2(int i) {...}

}

class Test
{
static void Main() {
D cd1 = new D(C.M1); // M1
D cd2 = new D(C.M2); // M2
D cd3 = cd1 + cd2; // M1 + M2
D cd4 = cd3 + cd1; // M1 + M2 + M1
D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2
}

}

При создании экземпляров cd1 и cd2 каждый из них инкапсулирует один метод. При создании экземпляра cd3 его список вызова содержит два метода, M1 и M2, в этом порядке. Список вызова экземпляра cd4 содержит методы M1, M2 и M1, в этом порядке. Наконец, список вызова экземпляра cd5 содержит методы M1, M2, M1, M1 и M2, в этом порядке. Дополнительные примеры объединения (а также удаления) делегатов см. в §15.4.

15.2 Совместимость делегатов

Метод или делегат M совместим с типом делегата D, если верны все следующие условия:

· D и M имеют одинаковое число параметров, и каждый параметр в D имеет такие же модификаторы ref или out, как и соответствующий параметр в M;

· для каждого параметра значения (параметр без модификатора ref или out) существует преобразование идентификации (§6.1.1) или неявное преобразование ссылки (§6.1.6) из типа параметра в D в соответствующий тип параметра в M;

· для каждого параметра ref или out тип параметра в D такой же, как тип параметра в M;

· существует преобразование идентификации или неявное преобразование ссылки из типа возвращаемого значения M в тип возвращаемого значения D.

15.3 Создание экземпляра делегата

Экземпляр делегата создается выражением_создания_делегата (§7.5.10.5) или преобразованием в тип делегата. Вновь созданный экземпляр делегата затем ссылается на одно из следующего:

· на статический метод, на который имеется ссылка в выражении_создания_делегата, или

· на целевой объект (который не может иметь значение null) и метод экземпляра, на которые имеются ссылки в выражении_создания_делегата, или

· на другой делегат.

Например:

delegate void D(int x);

class C
{
public static void M1(int i) {...}
public void M2(int i) {...}
}

class Test
{
static void Main() {
D cd1 = new D(C.M1); // статический метод
C t = new C();
D cd2 = new D(t.M2); // метод экземпляра
D cd3 = new D(cd2); // другой делегат
}
}

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

15.4 Вызов делегата

Язык C# предоставляет специальный синтаксис для вызова делегата. Когда вызывается непустой экземпляр делегата, чей список вызова содержит одну запись, он вызывает один метод с теми же аргументами, которые были ему заданы, и возвращает то же самое значение, что метод, на который имеется ссылка. (Подробные сведения о вызове делегата см. в §7.5.5.3). Если при вызове такого делегата происходит исключение, и это исключение не перехватывается в вызванном методе, поиск предложения перехвата исключения продолжается в методе, вызвавшем делегат, как если бы этот метод напрямую вызвал метод, на который ссылался делегат.

Вызов экземпляра делегата, чей список вызова содержит несколько записей, продолжается путем вызова каждого из методов в списке вызова, одновременно, по порядку. Каждому из вызванных таким образом методов передается тот же набор параметров, заданный экземпляру делегата. Если такой вызов делегата включает параметры ссылок (§10.6.1.2), каждый вызов метода происходит со ссылкой на ту же самую переменную; изменения этой переменной, сделанные одним методом в списке вызова, будут видимы для методов, находящихся далее в списке вызова. Если вызов делегата включает выходные параметры или возвращаемое значение, их окончательное значение будет получено от вызова последнего делегата в списке.

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

Попытка вызова экземпляра делегата, имеющего значение null, приводит к исключению типа System.NullReferenceException.

В следующем примере показано, как создавать экземпляры, объединять, удалять и вызывать делегаты:

using System;

delegate void D(int x);

class C
{
public static void M1(int i) {
Console.WriteLine("C.M1: " + i);
}

public static void M2(int i) {
Console.WriteLine("C.M2: " + i);
}

public void M3(int i) {
Console.WriteLine("C.M3: " + i);
}
}

class Test
{
static void Main() {
D cd1 = new D(C.M1);
cd1(-1); // вызов M1

D cd2 = new D(C.M2);
cd2(-2); // вызов M2

D cd3 = cd1 + cd2;
cd3(10); // вызов M1, а затем M2

cd3 += cd1;
cd3(20); // вызов M1, M2, а затем M1

C c = new C();
D cd4 = new D(c.M3);
cd3 += cd4;
cd3(30); // вызов M1, M2, M1, а затем M3

cd3 -= cd1; // удаление последнего M1
cd3(40); // вызов M1, M2, а затем M3

cd3 -= cd4;
cd3(50); // вызов M1, а затем M2

cd3 -= cd2;
cd3(60); // вызов M1

cd3 -= cd2; // невозможность удаления неопасна
cd3(60); // вызов M1

cd3 -= cd1; // список вызова пуст, поэтому cd3 имеет значение null

// cd3(70); // возникло исключение System.NullReferenceException

cd3 -= cd1; // невозможность удаления неопасна
}
}

Как показано в выражении cd3 += cd1;, делегат может присутствовать в списке вызова несколько раз. В этом случае он просто вызывается один раз при каждом появлении. В таком списке вызова, как этот, при удалении делегата фактически удаляется его последнее появление в списке вызова.

Непосредственно перед выполнением последнего оператора, cd3 -= cd1;, делегат cd3 ссылается на пустой список вызова. Попытка удаления делегата из пустого списка (или удаления несуществующего делегата из непустого списка) не является ошибкой.

В результате получается:

C.M1: -1
C.M2: -2
C.M1: 10
C.M2: 10
C.M1: 20
C.M2: 20
C.M1: 20
C.M1: 30
C.M2: 30
C.M1: 30
C.M3: 30
C.M1: 40
C.M2: 40
C.M3: 40
C.M1: 50
C.M2: 50
C.M1: 60
C.M1: 60


16. Исключения

Исключения в языке C# обеспечивают структурированный, единообразный и строго типизированный способ обработки состояний ошибки, как на системном уровне, так и на уровне приложения. Механизм исключения в языке C# вполне сходен с механизмом в языке C++, с несколькими важными отличиями:

· в C# все исключения должны быть представлены экземпляром типа класса, производным от System.Exception. В C++ для представления исключения может использоваться любое значение любого типа;

· в C# блок finally (§8.10) может использоваться для записи кода завершения, который выполняется как при нормальном выполнении, так и при исключительных состояниях. Такой код труден для написания в C++ без дублирования кода;

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

16.1 Причины исключений

Исключения могут вызываться двумя разными способами.

· Оператор throw (§8.9.5) вызывает исключение немедленно и безусловно. Управление никогда не передается оператору, следующему за оператором throw.

· Определенные исключительные состояния, возникающие при обработке операторов и выражения C#, служат причиной исключения в определенных обстоятельствах, когда операцию не удается выполнить нормально. Например, операция целочисленного деления (§7.7.2) создает исключение System.DivideByZeroException, если знаменатель равен нулю. Список различных исключений, которые могут возникнуть таким образом, см. в §16.4.

System.Exception

· Message — свойство только для… · InnerException — свойство… Значения этих…

16.3 Обработка исключений

Исключения обрабатываются оператором try (§8.10).

Когда происходит исключение, система ищет ближайшее предложение catch, которое может обработать исключение, как это определено типом времени выполнения исключения. Сначала в текущем методе выполняется поиск лексически объемлющего оператора try, и связанные предложения catch оператора try рассматриваются по порядку. Если это не удается, в методе, вызвавшем текущий метод, выполняется поиск лексически объемлющего оператора try, который содержит точку вызова текущего метода. Этот поиск продолжается, пока не будет найдена конструкция catch, которая может обработать текущее исключение, назвав имя класса исключения такого же класса или базового класса в выполняемом типе созданного исключения. Конструкция catch, которая не именует класс исключения, может обрабатывать любое исключение.

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

Если соответствующая конструкция catch не найдена, выполняется одно из следующих двух действий:

· если поиск соответствующей конструкции catch доходит до статического конструктора (§10.12) или инициализатора статического поля, создается исключение System.TypeInitializationException в точке, запустившей вызов статического конструктора. Внутреннее исключение System.TypeInitializationException содержит то исключение, которое возникло первоначально;

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

Исключения, происходящие при выполнении деструктора, заслуживают особого упоминания. Если исключение происходит при выполнения деструктора и не перехватывается, выполнение этого деструктора завершается и вызывается деструктор базового класса (если он имеется). Если нет базового класса (как в случае типа object) или нет деструктора базового класса, исключение удаляется.

16.4 Общие классы исключений

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

 

System.ArithmeticException Базовый класс для исключений, происходящих при арифметических операциях, например System.DivideByZeroException и System.OverflowException.
System.ArrayTypeMismatchException Вызывается при ошибке сохранения в массиве, оттого что фактический тип сохраняемого элемента несовместим с фактическим типом массива.
System.DivideByZeroException Вызывается при попытке деления целого значения на нуль.
System.IndexOutOfRangeException Вызывается при попытке индексировать массив индексом меньше нуля или выходящим за границы массива.
System.InvalidCastException Вызывается при ошибке во время выполнения явного преобразования из базового типа или интерфейса в производный тип.
System.NullReferenceException Вызывается, если пустая ссылка используется таким образом, что требуется объект, на который указывает ссылка.
System.OutOfMemoryException Вызывается при неудачной попытке выделения памяти (с помощью оператора new).
System.OverflowException Вызывается при переполнении в арифметической операции в контексте checked.
System.StackOverflowException Вызывается при исчерпании стека выполнения из-за слишком большого числа отложенных вызовов методов; обычно это указывает на очень глубокую или неограниченную рекурсию.
System.TypeInitializationException Вызывается, когда статический конструктор создает исключение и нет предложений catch для его перехвата.

 


17. Атрибуты

Многое в языке C# дает возможность программисту указывать декларативные сведения о сущностях, определенных в программе. Например, доступность метода в классе указывается путем добавления к нему модификаторов_метода public, protected, internal и private.

Язык C# позволяет программистам создавать новые виды декларативных сведений, называемых атрибутами. Программист может присоединить эти атрибуты к различным программным сущностям и извлекать сведения атрибута в среде выполнения. Например, структура может определить атрибут HelpAttribute, который можно поместить в определенные программные элементы (такие как классы и методы), чтобы обеспечить отображение из этих программных элементов в соответствующую им документацию.

Атрибуты определяются посредством объявления классов атрибутов (§17.1), которые могут иметь позиционные и именованные параметры (§17.1.2). Атрибуты вложены в сущности в программе C# с помощью спецификаций атрибутов (§17.2) и могут извлекаться во время выполнения как экземпляры атрибутов (§17.3).

17.1 Классы атрибутов

Класс, который прямо или косвенно производится от абстрактного класса System.Attribute, является классом атрибута. Объявление класса атрибута определяет новый вид атрибута, который можно поместить в объявлении. Классы атрибутов принято именовать с помощью суффикса Attribute. При использовании атрибута этот суффикс можно либо включать, либо опускать.

17.1.1 Использование атрибутов

Атрибут AttributeUsage (§17.4.1) применяется для описания, как использовать класс атрибута.

Атрибут AttributeUsage имеет позиционный параметр (§17.1.2), позволяющий классу атрибута указать те виды объявлений, в которых можно использовать атрибут. Пример:

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute: Attribute
{
...
}

В этом примере определяется класс атрибута с именем SimpleAttribute, который можно помещать только в объявления_классов и объявления_интерфейсов. В примере:

[Simple] class Class1 {...}

[Simple] interface Interface1 {...}

показано несколько возможностей использования атрибута Simple. Хотя этот атрибут определен с именем SimpleAttribute, при его использовании можно опускать суффикс Attribute, то есть пользоваться коротким именем Simple. Таким образом, приведенный выше пример семантически эквивалентен следующему:

[SimpleAttribute] class Class1 {...}

[SimpleAttribute] interface Interface1 {...}

Атрибут AttributeUsage имеет именованный параметр (§17.1.2), называемый AllowMultiple, который указывает, можно ли задать атрибут более одного раза для данной сущности. Если параметр AllowMultiple для класса атрибута имеет значение True, то этот класс атрибута является классом атрибута многократного использования и его можно указывать для сущности более одного раза. Если параметр AllowMultiple для класса атрибута имеет значение False, то этот класс атрибута является классом атрибута однократного использования и его можно указывать для сущности не более одного раза.

В примере:

using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorAttribute: Attribute
{
private string name;

public AuthorAttribute(string name) {
this.name = name;
}

public string Name {
get { return name; }
}
}

определен класс атрибута многократного использования с именем AuthorAttribute. В примере:

[Author("Brian Kernighan"), Author("Dennis Ritchie")]
class Class1
{
...
}

показано объявление класса с двумя использованиями атрибута Author.

Атрибут AttributeUsage имеет другой именованный параметр, называемый Inherited, который указывает, наследуется ли указанный для базового класса атрибут также классами, производными от этого базового класса. Если параметр Inherited для класса атрибута имеет значение True, этот атрибут наследуется. Если параметр Inherited для класса атрибута имеет значение False, этот атрибут не наследуется. Если параметр не установлен, его значение по умолчанию True.

Класс атрибута X, не имеющий вложенного в него атрибута AttributeUsage, как в следующем примере:

using System;

class X: Attribute {...}

эквивалентен следующему:

using System;

[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X: Attribute {...}

17.1.2 Позиционные и именованные параметры

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

Пример:

using System;

[AttributeUsage(AttributeTargets.Class)]
public class HelpAttribute: Attribute
{
public HelpAttribute(string url) { // Позиционный параметр
...
}

public string Topic { // Именованный параметр
get {...}
set {...}
}

public string Url {
get {...}
}
}

В примере определен класс атрибута с именем HelpAttribute, имеющий один позиционный параметр url и один именованный параметр Topic. Хотя свойство Url не статическое и общее, оно не определяет именованный параметр, так как не является доступным для чтения и записи.

Этот класс атрибута можно использовать следующим образом:

[Help("http://www.mycompany.com/.../Class1.htm")]
class Class1
{
...
}

[Help("http://www.mycompany.com/.../Misc.htm", Topic = "Class2")]
class Class2
{
...
}

17.1.3 Типы параметров атрибута

Типы позиционных и именованных параметров для класса атрибута ограничены типами параметров атрибута, к которым относятся:

· один из следующих типов: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong и ushort;

· тип object;

· тип System.Type;

· перечисляемый тип, если и он имеет общую доступность, и типы, в которые он вложен (если вложен), также имеют общую доступность (§17.2);

· одномерные массивы указанных выше типов.

· Аргумент конструктора или общее поле, которые не имеют один из этих типов, не могут использоваться в качестве позиционных или именованных параметров в спецификации атрибута.

17.2 Спецификация атрибута

Спецификация атрибута — это применение предварительно определенного атрибута к объявлению. Атрибут является частью дополнительных декларативных сведений, задаваемых для объявления. Атрибуты могут задаваться в глобальной области видимости (чтобы задать атрибуты для содержащей сборки или модуля) и для объявлений_типов (§9.6), объявлений_членов_класса (§10.1.5), объявлений_членов_интерфейса (§13.2), объявлений_членов_структуры (§11.2), объявлений_членов_перечисления (§14.3), объявлений_методов_доступа (§10.7.2), объявлений_метода_доступа_к_событиям (§10.8.1) и списков_формальных_параметров (§10.6.1).

Атрибуты задаются в разделах атрибутов. Раздел атрибутов состоит из пары квадратных скобок, которые окружают список из одного или более атрибутов, разделенных запятыми. Порядок указания атрибутов в таком списке и порядок размещения разделов, вложенных в одну и ту же программную сущность, не имеет значения. Например, спецификации атрибутов [A][B], [B][A], [A, B] и [B, A] эквивалентны.

глобальные_атрибуты:
разделы_глобальных_атрибутов

разделы_глобальных_атрибутов:
раздел_глобальных_атрибутов
разделы_глобальных_атрибутов раздел_глобальных_атрибутов

раздел_глобальных_атрибутов:
[ описатель_целевого_объекта_глобального_атрибута список_атрибутов ]
[ описатель_целевого_объекта_глобального_атрибута список_атрибутов , ]

описатель_целевого_объекта_глобального_атрибута:
целевой_объект_глобального_атрибута :

целевой_объект_глобального_атрибута:
assembly
module

атрибуты:
разделы_атрибутов

разделы_атрибутов:
раздел_атрибутов
разделы_атрибутов раздел_атрибутов

раздел_атрибутов:
[ описатель_целевого_объекта_атрибутанеоб список_атрибутов ]
[ описатель_целевого_объекта_атрибутанеоб список_атрибутов , ]

описатель_целевого_объекта_атрибута:
целевой_объект_атрибута :

целевой_объект_атрибута:
field
event
method
param
property
return
type

список_атрибутов:
атрибут
список_атрибутов , атрибут

атрибут:
имя_атрибута аргументы_атрибутанеоб

имя_атрибута:
имя_типа

аргументы_атрибута:
( список_позиционных_аргументовнеоб )
( список_позиционных_аргументов , список_именованных_аргументов )
( список_именованных_аргументов )

список_позиционных_аргументов:
позиционный_аргумент
список_позиционных_аргументов , позиционный_аргумент

позиционный_аргумент:
выражение_аргумента_атрибута

список_именованных_аргументов:
именованный_аргумент
список_именованных_аргументов , именованный_аргумент

именованный_аргумент:
идентификатор = выражение_аргумента_атрибута

выражение_аргумента_атрибута:
выражение

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

Имя_атрибута идентифицирует класс атрибута. Если имя_атрибута имеет вид имени_типа, это имя должно ссылаться на класс атрибута. Иначе возникает ошибка времени компиляции. Например:

class Class1 {}

[Class1] class Class2 {} // Ошибка

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

Определенные контексты разрешают спецификацию атрибута более чем для одного целевого объекта. Программа может явно задать целевой объект включением описателя_целевого_объекта_атрибута. Если атрибут размещен на глобальном уровне, требуется описатель_целевого_объекта_глобального_атрибута. Во всех остальных расположениях применяется обоснованное значение по умолчанию, но описатель_целевого_объекта_атрибута может использоваться для подтверждения или переопределения значения по умолчанию в определенных неоднозначных ситуациях (или просто для подтверждения значения по умолчанию в не вызывающих сомнения случаях). Таким образом, описатели_целевого_объекта_атрибута обычно могут быть опущены, кроме случаев их использования на глобальном уровне. В случае потенциально неоднозначных контекстов решение принимается следующим образом:

· атрибут, заданный в глобальной области видимости, может применяться либо к конечной сборке, либо к конечному модулю. Для этого контекста нет значения по умолчанию, поэтому в этом контексте всегда требуется описатель_целевого_объекта_атрибута. Наличие описателя_целевого_объекта_атрибута assembly указывает, что атрибут применяется к конечной сборке; наличие описателя_целевого_объекта_атрибута module указывает, что атрибут применяется к конечному модулю;

· атрибут, заданный в объявлении делегата, может применяться либо к объявляемому делегату, либо к его возвращаемому значению. При отсутствии описателя_целевого_объекта_атрибута атрибут применяется к делегату. Наличие описателя_целевого_объекта_атрибута type указывает, что атрибут применяется к делегату; наличие описателя_целевого_объекта_атрибута return указывает, что атрибут применяется к возвращаемому значению;

· атрибут, заданный в объявлении метода, может применяться либо к объявляемому методу, либо к его возвращаемому значению. При отсутствии описателя_целевого_объекта_атрибута атрибут применяется к методу. Наличие описателя_целевого_объекта_атрибута method указывает, что атрибут применяется к методу; наличие описателя_целевого_объекта_атрибута return указывает, что атрибут применяется к возвращаемому значению;

· атрибут, заданный в объявлении оператора, может применяться либо к объявляемому оператору, либо к его возвращаемому значению. При отсутствии описателя_целевого_объекта_атрибута атрибут применяется к оператору. Наличие описателя_целевого_объекта_атрибута method указывает, что атрибут применяется к методу; наличие описателя_целевого_объекта_атрибута return указывает, что атрибут применяется к возвращаемому значению;

· атрибут, заданный в объявлении события, в котором опущены методы доступа к событиям, может применяться к объявляемому событию, к связанному полю (если событие не является абстрактным) или к связанным методам add и remove. При отсутствии описателя_целевого_объекта_атрибута атрибут применяется к событию. Наличие описателя_целевого_объекта_атрибута event указывает, что атрибут применяется к событию; наличие описателя_целевого_объекта_атрибута field указывает, что атрибут применяется к полю; а наличие описателя_целевого_объекта_атрибута method указывает, что атрибут применяется к методам;

· атрибут, заданный в объявлении метода доступа get для объявления свойства или индексатора, может применяться либо к связанному методу, либо к его возвращаемому значению. При отсутствии описателя_целевого_объекта_атрибута атрибут применяется к методу. Наличие описателя_целевого_объекта_атрибута method указывает, что атрибут применяется к методу; наличие описателя_целевого_объекта_атрибута return указывает, что атрибут применяется к возвращаемому значению;

· атрибут, указанный в объявлении метода доступа set для объявления свойства или индексатора, может применяться либо к связанному методу, либо к его одиночному неявному параметру. При отсутствии описателя_целевого_объекта_атрибута атрибут применяется к методу. Наличие описателя_целевого_объекта_атрибута method указывает, что атрибут применяется к методу; наличие описателя_целевого_объекта_атрибута param указывает, что атрибут применяется к параметру; наличие описателя_целевого_объекта_атрибута return указывает, что атрибут применяется к возвращаемому значению;

· атрибут, заданный в объявлении метода доступа add или remove для объявления события, может применяться или к связанному методу, или к его одиночному параметру. При отсутствии описателя_целевого_объекта_атрибута атрибут применяется к методу. Наличие описателя_целевого_объекта_атрибута method указывает, что атрибут применяется к методу; наличие описателя_целевого_объекта_атрибута param указывает, что атрибут применяется к параметру; наличие описателя_целевого_объекта_атрибута return указывает, что атрибут применяется к возвращаемому значению.

В других контекстах включение описателя_целевого_объекта_атрибута разрешено, но излишне. Например, объявление класса может или включать, или опускать описатель type:

[type: Author("Brian Kernighan")]
class Class1 {}

[Author("Dennis Ritchie")]
class Class2 {}

Является ошибкой задание недопустимого описателя_целевого_объекта_атрибута. Например, описатель param нельзя использовать в объявлении класса:

[param: Author("Brian Kernighan")] // Ошибка
class Class1 {}

Классы атрибутов принято именовать с помощью суффикса Attribute. Имя_атрибута вида имя_типа может или включать, или опускать этот суффикс. Если обнаружен класс атрибута и с этим суффиксом, и без суффикса, эта неоднозначность приводит к ошибке времени компиляции. Если имя_атрибута записывается таким образом, что его самый правый идентификатор является буквальным идентификатором (§2.4.2), то сопоставляется только атрибут без суффикса, позволяя таким образом разрешить неоднозначность. Пример:

using System;

[AttributeUsage(AttributeTargets.All)]
public class X: Attribute
{}

[AttributeUsage(AttributeTargets.All)]
public class XAttribute: Attribute
{}

[X] // Ошибка: неоднозначность
class Class1 {}

[XAttribute] // Ссылается на XAttribute
class Class2 {}

[@X] // Ссылается на X
class Class3 {}

[@XAttribute] // Ссылается на XAttribute
class Class4 {}

Здесь показаны два класса атрибутов с именами X и XAttribute. Атрибут [X] является неоднозначным, так как он может ссылаться или на X, или на XAttribute. Использование буквального идентификатора позволяет указать точное намерение в таких редких случаях. Атрибут [XAttribute] не является неоднозначным (хотя он мог бы им быть, если бы имелся класс атрибута с именем XAttributeAttribute). Если удаляется объявление для класса X, то оба атрибута ссылаются на класс атрибута с именем XAttribute, как это показано в следующем примере:

using System;

[AttributeUsage(AttributeTargets.All)]
public class XAttribute: Attribute
{}

[X] // Ссылается на XAttribute
class Class1 {}

[XAttribute] // Ссылается на XAttribute
class Class2 {}

[@X] // Ошибка: нет атрибута с именем "X"
class Class3 {}

Использование класса атрибута одноразового использования более одного раза для одной и той же сущности является ошибкой времени компиляции. Пример:

using System;

[AttributeUsage(AttributeTargets.Class)]
public class HelpStringAttribute: Attribute
{
string value;

public HelpStringAttribute(string value) {
this.value = value;
}

public string Value {
get {...}
}
}

[HelpString("Описание Class1")]
[HelpString("Другое описание Class1")]
public class Class1 {}

В результате возникает ошибка времени компиляции, так как предпринимается попытка использования HelpString, который является классом атрибута одноразового использования, более одного раза в объявлении Class1.

Выражение E является выражением_аргумента_атрибута, если верны все следующие утверждения:

· тип E — это тип параметра атрибута (§17.1.3);

· во время компиляции значение E может быть разрешено в одно из следующего:

o постоянное значение;

o объект System.Type;

o одномерный массив выражений_аргумента_атрибута.

Например:

using System;

[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute: Attribute
{
public int P1 {
get {...}
set {...}
}

public Type P2 {
get {...}
set {...}
}

public object P3 {
get {...}
set {...}
}
}

[Test(P1 = 1234, P3 = new int[] {1, 3, 5}, P2 = typeof(float))]
class MyClass {}

Выражение_typeof (§7.5.11), используемое в качестве выражения аргумента атрибута, может ссылаться на неуниверсальный тип, закрытый сконструированный тип или на несвязанный универсальный тип, но не может ссылаться на открытый тип. Это обеспечивает возможность разрешения выражения во время компиляции.

class A: Attribute
{
public A(Type t) {...}
}

class G<T>
{
[A(typeof(T))] T t; // Ошибка, открытый тип в атрибуте
}

class X
{
[A(typeof(List<int>))] int x; // Все правильно, закрытый сконструированный тип
[A(typeof(List<>))] int y; // Все правильно, несвязанный универсальный тип
}

17.3 Экземпляры атрибутов

Экземпляр атрибута — это экземпляр, представляющий атрибут во время выполнения. Атрибут определяется с помощью класса атрибута, позиционных аргументов и именованных аргументов. Экземпляр атрибута — это экземпляр класса атрибута, который инициализируется с помощью позиционных и именованных аргументов.

Извлечение экземпляра атрибута включает обработку как во время компиляции, так и во время выполнения, как описано в следующих разделах.

17.3.1 Компиляция атрибута

Компиляция атрибута с классом атрибута T, списком_позиционных_аргументов P и списком_именованных_аргументов N состоит из следующих шагов:

· выполнение шагов обработки времени компиляции для компиляции выражения_создания_объекта вида new T(P). Результатом этих шагов является или ошибка времени компиляции, или определение конструктора экземпляров C для T, который может быть вызван во время выполнения;

· если у C нет общей доступности, возникает ошибка времени компиляции;

· для каждого именованного_аргумента Arg в N:

o разрешение Name быть идентификатором именованного_аргумента Arg;

o Name должен идентифицировать не статическое общее с доступом для чтения и записи поле или свойство в T. Если в T нет такого поля или свойства, возникает ошибка времени компиляции.

· сохранение следующих сведений для создания экземпляра атрибута во время выполнения: класс атрибута T, конструктор экземпляров C в T, список_позиционных_аргументов P и список_именованных_аргументов N.

17.3.2 Извлечение экземпляра атрибута во время выполнения

Компиляция атрибута выдает класс атрибута T, конструктор экземпляров C в T, список_позиционных_аргументов P и список_именованных_аргументов N. Если даны эти сведения, экземпляр атрибута может быть извлечен во время выполнения с помощью следующих шагов:

· выполнение шагов обработки времени выполнения для выполнения выражения_создания_объекта вида new T(P) с помощью конструктора экземпляров C, как определено во время компиляции. Эти шаги приводят либо к исключению, либо к созданию экземпляра O объекта T;

· для каждого именованного_аргумента Arg в N в таком порядке:

o разрешение Name быть идентификатором именованного_аргумента Arg. Если Name не идентифицирует не статическое, общее, с доступом для чтения и записи поле или свойство в O, вызывается исключение;

o разрешение Value быть результатом вычисления выражения_аргумента_атрибута Arg;

o если Name идентифицирует поле в O, установить это поле в Value;

o иначе Name идентифицирует свойство в O. Установить это свойство в Value;

o результатом является O, экземпляр класса атрибута T, инициализированный с помощью списка_позиционных_аргументов P и списка_именованных_аргументов N.

17.4 Зарезервированные атрибуты

Небольшое число атрибутов некоторым образом влияют на язык. К этим атрибутам относятся:

· System.AttributeUsageAttribute (§17.4.1), используемый для описания способов, которыми можно использовать класс атрибута;

· System.Diagnostics.ConditionalAttribute (§17.4.2), используемый для определения условных методов;

· System.ObsoleteAttribute (§17.4.3), используемый для пометки члена как устаревшего.

AttributeUsage

Класс, к которому… namespace System { [AttributeUsage(AttributeTargets.Class)] public class… public virtual bool AllowMultiple { get {...} set {...} }

Conditional

namespace System.Diagnostics { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public class… public string ConditionString { get {...} } } }

17.4.2.1 Условные методы

Метод, к которому добавлен атрибут Conditional, является условным методом. Атрибут Conditional указывает условие путем проверки символа условной компиляции. Вызовы условного метода или включаются, или опускаются, в зависимости от того, определен ли этот символ в точке вызова. Если символ определен, вызов включается; иначе вызов (включая оценку параметров вызова) опускается.

Для условного метода имеются следующие ограничения:

· условный метод должен быть методом в объявлении_класса или объявлении_структуры. Если атрибут Conditional указан для метода в объявлении интерфейса, возникает ошибка времени компиляции;

· условный метод должен иметь тип возвращаемого значения void;

· условный метод не должен быть помечен модификатором override. Однако условный метод может быть помечен модификатором virtual. Переопределения такого метода являются неявно условными и они не должны явно помечаться атрибутом Conditional;

· условный метод не должен быть реализацией метода интерфейса. Иначе возникает ошибка времени компиляции.

Кроме того, ошибка времени компиляции возникает, если условный метод используется в выражении_создания_делегата. Пример:

#define DEBUG

using System;
using System.Diagnostics;

class Class1
{
[Conditional("DEBUG")]
public static void M() {
Console.WriteLine("Executed Class1.M");
}
}

class Class2
{
public static void Test() {
Class1.M();
}
}

Здесь Class1.M объявлен как условный метод. Метод Test класса Class2 вызывает этот метод. Так как символ условной компиляции DEBUG определен, если вызывается метод Class2.Test, то он вызовет M. Если бы символ DEBUG не был определен, то метод Class2.Test не вызвал бы Class1.M.

Обратите внимание, что включение или исключение вызова условного метода управляется символами условной компиляции в точке вызова. Пример:

Файл class1.cs:

using System.Diagnostics;

class Class1
{
[Conditional("DEBUG")]
public static void F() {
Console.WriteLine("Выполнен Class1.F");
}
}

Файл class2.cs:

#define DEBUG

class Class2
{
public static void G() {
Class1.F(); // Вызывается F
}
}

Файл class3.cs:

#undef DEBUG

class Class3
{
public static void H() {
Class1.F(); // F не вызывается
}
}

Здесь каждый из классов Class2 и Class3 содержит вызовы условного метода Class1.F, обусловленного тем, определен или нет символ DEBUG. Так как этот символ определен в контексте Class2, но не в Class3, вызов F в Class2 включен, тогда как вызов F в Class3 опущен.

Использование условных методов в цепочке наследования может привести к путанице. Вызовы условного метода посредством base в виде base.M подчиняются обычным правилам вызова условного метода. Рассмотрим следующий пример:

Файл class1.cs:

using System;
using System.Diagnostics;

class Class1
{
[Conditional("DEBUG")]
public virtual void M() {
Console.WriteLine("Class1.M выполнен");
}
}

Файл class2.cs:

using System;

class Class2: Class1
{
public override void M() {
Console.WriteLine("Class2.M выполнен");
base.M(); // base.M не вызывается!
}
}

Файл class3.cs:

#define DEBUG

using System;

class Class3
{
public static void Test() {
Class2 c = new Class2();
c.M(); // M вызывается
}
}

Здесь Class2 включает вызов метода M, определенного в его базовом классе. Этот вызов опускается, так как базовый метод является условным, базированным на наличии символа DEBUG, который здесь не определен. Таким образом, метод записывает на консоль только сообщение «Class2.M выполнен». Разумное использование ПРО_описаний может устранить такие проблемы.

17.4.2.2 Классы условных атрибутов

Класс атрибута (§17.1), к которому добавлен один или несколько атрибутов Conditional, является классом условных атрибутов. Таким образом, класс условных атрибутов связывается с символами условной компиляции, объявленными в его атрибутах Conditional. Рассмотрим следующий пример:

using System;
using System.Diagnostics;
[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}

Здесь объявлен атрибут TestAttribute в качестве класса условных атрибутов, связанного с символами условной компиляции ALPHA и BETA.

Спецификации атрибута (§17.2) условного атрибута включаются, если в точке спецификации определен один или несколько его связанных символов условной компиляции, иначе спецификация атрибута опускается.

Обратите внимание, что включение или исключение спецификации атрибута класса условных атрибутов управляется символами условной компиляции в точке спецификации. Рассмотрим следующий пример:

Файл test.cs:

using System;
using System.Diagnostics;

[Conditional(“DEBUG”)]

public class TestAttribute : Attribute {}

Файл class1.cs:

#define DEBUG

[Test] // TestAttribute специфицирован

class Class1 {}

Файл class2.cs:

#undef DEBUG

[Test] // TestAttribute не специфицирован

class Class2 {}

Здесь к каждому из классов Class1 и Class2 добавляется атрибут Test, который является условным и зависит от того, определен или нет символ DEBUG. Так как этот символ определен в контексте Class1, но не в контексте Class2, спецификация атрибута Test для Class1 включена, тогда как спецификация атрибута Test для Class2 опущена.

Obsolete

namespace System { [AttributeUsage( AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | … public ObsoleteAttribute(string message) {...} public ObsoleteAttribute(string message, bool error) {...}

17.5 Атрибуты для взаимодействия

Примечание. Этот раздел применим только для реализации Microsoft .NET языка C#.

COM и Win32

NETАтрибут IndexerName

namespace System.Runtime.CompilerServices.CSharp { [AttributeUsage(AttributeTargets.Property)] public class IndexerNameAttribute: Attribute { … public string Value { get {...} } } }

18. Небезопасный код

Базовый язык C#, как определено в предыдущих главах, заметно отличается от C и C++ тем, что в нем не используются указатели в качестве типа данных. Вместо этого C# предоставляет ссылки и возможность создавать объекты, управляемые сборщиком мусора. Эта разработка в сочетании с другими особенностями делает язык C# гораздо более безопасным, чем C и C++. В базовом языке C# просто невозможно иметь неинициализированную переменную, «висящий» указатель или выражение, индексирующее массив за пределами его границ. Таким образом устраняются целые категории ошибок, регулярно досаждающих в программах на C и C++.

Хотя практически у каждой конструкции с типом указателя в C или C++ имеется аналог ссылочного типа в C#, тем не менее в некоторых ситуациях доступ к типам указателей становится необходимостью. Например, согласование с операционной системой, доступ к устройству, сопоставленному памяти, или реализация критического по времени алгоритма могут оказаться неосуществимыми без доступа к указателям. Для удовлетворения этой потребности C# предоставляет возможность писать небезопасный код.

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

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

18.1 Небезопасные контексты

Небезопасные возможности C# доступны только в небезопасных контекстах. Небезопасный контекст вводится включением модификатора unsafe в объявление типа или члена, или использованием небезопасного_оператора:

· объявление класса, структуры, интерфейса или делегата может включать модификатор unsafe, в этом случае все текстовое пространство этого объявления (включая тело класса, структуры или интерфейса) считается небезопасным контекстом;

· объявление поля, метода, свойства, события, индексатора, оператора, конструктора экземпляров, деструктора или статического конструктора может включать модификатор unsafe, в этом случае все текстовое пространство этого объявления члена считается небезопасным контекстом;

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

Соответствующие грамматические расширения показаны ниже. Многоточиями (...) для краткости представлены порождения, описанные в предыдущих главах.

модификатор_класса:
...
unsafe

модификатор_структуры:
...
unsafe

модификатор_интерфейса:
...
unsafe

модификатор_делегата:
...
unsafe

модификатор_поля:
...
unsafe

модификатор_метода:
...
unsafe

модификатор_свойства:
...
unsafe

модификатор_события:
...
unsafe

модификатор_индексатора:
...
unsafe

модификатор_оператора:
...
unsafe

модификатор_конструктора:
...
unsafe

объявление_деструктора:
атрибутынеоб externнеоб unsafeнеоб ~ идентификатор ( ) тело_деструктора
атрибутынеоб unsafeнеоб externнеоб ~ идентификатор ( ) тело_деструктора

модификаторы_статического_конструктора:
externнеоб unsafeнеоб static
unsafeнеоб externнеоб static
externнеоб static unsafeнеоб
unsafeнеоб static externнеоб
static externнеоб unsafeнеоб
static unsafeнеоб externнеоб

внедренный_оператор:
...
небезопасный_оператор

небезопасный_оператор:
unsafe блок

В примере

public unsafe struct Node
{
public int Value;
public Node* Left;
public Node* Right;
}

модификатор unsafe, указанный в объявлении структуры, делает все текстовое пространство объявления структуры небезопасным контекстом. Таким образом можно объявить поля Left и Right с типом указателя. Этот пример можно было бы записать так:

public struct Node
{
public int Value;
public unsafe Node* Left;
public unsafe Node* Right;
}

Здесь модификаторы unsafe в объявлениях полей делают эти объявления небезопасными контекстами.

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

public class A
{
public unsafe virtual void F() {
char* p;
...
}
}

public class B: A
{
public override void F() {
base.F();
...
}
}

модификатор unsafe метода F в классе A просто делает текстовое пространство F небезопасным контекстом, в котором можно использовать небезопасные возможности языка. В переопределении F в классе B нет необходимости вновь задавать модификатор unsafe, если, конечно, в самом методе F в классе B не требуются небезопасные возможности.

Ситуация несколько другая, когда тип указателя является частью подписи метода

public unsafe class A
{
public virtual void F(char* p) {...}
}

public class B: A
{
public unsafe override void F(char* p) {...}
}

Здесь, поскольку подпись F включает тип указателя, его можно записать только в небезопасном контексте. Однако небезопасный контекст можно ввести либо сделав небезопасным весь класс, как в случае A, либо включив модификатор unsafe в объявление метода, как в случае B.

18.2 Типы указателя

В небезопасном контексте тип (§4) может быть типом_указателем, а также типом_значением или ссылочным_типом. Однако тип_указателя можно также использовать в выражении typeof (§7.5.10.6) вне небезопасного контекста, так как такое использование не является небезопасным.

тип:
...
тип_указателя

Тип_указателя записывается как неуправляемый_тип или как зарезервированное слово void с последующей лексемой *:

тип_указателя:
неуправляемый_тип *
void *

неуправляемый_тип:
тип

Тип, указанный перед * в типе указателя, называется типом_референта типа указателя. Он представляет тип переменной, на которую указывает значение типа указателя.

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

Неуправляемый_тип — это любой тип, не являющийся ссылочным_типом и не содержащий поля ссылочного_типа на любом уровне вложенности. Иначе говоря, неуправляемым_типом является один из следующих типов:

· sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal и bool;

· любой перечисляемый_тип;

· любой тип_указателя;

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

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

Примеры типов указателей приведены в следующей таблице:

 

Пример Описание
byte* указатель на byte
char* указатель на char
int** указатель на указатель на int
int*[] одномерный массив указателей на int
void* указатель на неизвестный тип

 

В данной реализации все типы указателей должны иметь одинаковый размер и представление.

В отличие от C и C++, если несколько указателей объявлены в одном объявлении, в C# звездочка * записывается только вместе с базовым типом, а не как префиксный знак пунктуации с каждым именем указателя. Например:

int* pi, pj; // NOT как int *pi, *pj;

Значение указателя, имеющего тип T* представляет адрес переменной типа T. Оператор косвенного обращения к указателю * (§18.5.1) можно использовать для доступа к этой переменной. Например, если дана

переменная P типа int*, то выражение *P означает переменную типа int, находящуюся по адресу, содержащемуся в P.

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

Тип void* представляет указатель на неизвестный тип. Поскольку тип референта неизвестен, оператор косвенного обращения нельзя применить к указателю типа void*, и с таким указателем нельзя выполнять никакие вычисления. Однако указатель типа void* можно привести к любому другому типу указателя (и наоборот).

Типы указателя являются отдельной категорией типов. В отличие от ссылочных типов и типов значения, типы указателя не наследуют от object и нет никаких преобразований между типами указателя и object. В частности, упаковка и распаковка (§4.3) не поддерживаются для указателей. Однако допускаются преобразования между различными типами указателя и между типами указателя и целыми типами. Это описано в §18.4.

Тип_указателя можно использовать в качестве типа поля с модификатором volatile (§10.5.3).

Хотя указатели можно передавать в качестве параметров ref или out, это может привести к неопределенному поведению, поскольку указатель может быть установлен для указания на локальную переменную, которая больше не существует при возврате из вызванного метода, или если фиксированный объект, на который указывал указатель, больше не является фиксированным. Пример.

using System;

class Test
{
static int value = 20;

unsafe static void F(out int* pi1, ref int* pi2) {
int i = 10;
pi1 = &i;

fixed (int* pj = &value) {
// ...
pi2 = pj;
}
}

static void Main() {
int i = 10;
unsafe {
int* px1;
int* px2 = &i;

F(out px1, ref px2);

Console.WriteLine("*px1 = {0}, *px2 = {1}",
*px1, *px2); // неопределенное поведение
}
}
}

Метод может возвращать значение некоторого типа, и этот тип может быть указателем. Например, если задан указатель на непрерывную последовательность элементов типа int, счетчика этой последовательности элементов и некоторого другого значения типа int, следующий метод возвратит адрес этого значения в этой последовательности, если есть соответствие, иначе возвращается null:

unsafe static int* Find(int* pi, int size, int value) {
for (int i = 0; i < size; ++i) {
if (*pi == value)
return pi;
++pi;
}
return null;
}

В небезопасном контексте имеется несколько конструкций для операций с указателями:

· оператор * можно использовать для косвенного обращения к указателю (§18.5.1);

· оператор -> можно использовать для доступа к члену структуры посредством указателя (§18.5.2);

· оператор [] можно использовать для индексирования указателя (§18.5.3);

· оператор & можно использовать для получения адреса переменной (§18.5.4);

· операторы ++ и – можно использовать для увеличения и уменьшения указателей (§18.5.5);

· операторы + и – можно использовать для выполнения арифметических операций с указателем (§18.5.6);

· операторы ==, !=, <, >, <= и => можно использовать для сравнения указателей (§18.5.7);

· оператор stackalloc можно использовать для выделения памяти из стека вызова (§18.7);

· оператор fixed можно использовать для временной фиксации переменной, чтобы можно было получить ее адрес (§18.6).

18.3 Фиксированные и перемещаемые переменные

Оператор взятия адреса (§18.5.4) и оператор fixed (§18.6) разделяют переменные на две категории: фиксированные переменные и перемещаемые переменные.

Фиксированные переменные находятся в местах хранения, не затрагиваемых действием сборщика мусора. (Примерами фиксированных переменных являются локальные переменные, параметры по значению и переменные, созданные разыменованием указателей.) А перемещаемые переменные находятся в местах хранения, которые могут быть перемещены или удалены сборщиком мусора. (Примерами перемещаемых переменных являются поля в объектах и элементы массивов.)

Оператор & (§18.5.4) дает возможность без ограничений получать адрес фиксированной переменной. Но поскольку перемещаемая переменная может быть перемещена или удалена сборщиком мусора, адрес перемещаемой переменной можно получить только с помощью оператора fixed (§18.6), и этот адрес остается действительным только на время действия этого оператора fixed.

Точнее говоря, фиксированной переменной является одно из следующего:

· переменная, полученная в результате простого_имени (§7.5.2), относящегося к локальной переменной или параметру по значению, если только эта переменная не захвачена анонимной функцией;

· переменная, полученная в результате доступа_к_члену (§7.5.4) вида V.I, где V — фиксированная переменная типа_структуры;

· переменная, полученная в результате выражения_косвенного_обращения_по_указателю (§18.5.1) вида *P, доступа_к_члену_по_указателю (§18.5.2) вида P->I или доступа_к_элементу_по_указателю (§18.5.3) вида P[E].

Все другие переменные классифицируются как перемещаемые.

Обратите внимание, что статическое поле классифицируется как перемещаемая переменная. Также обратите внимание, что параметр ref или out классифицируется как перемещаемая переменная, даже если аргумент, предоставленный для параметра, является фиксированной переменной. И наконец, обратите внимание, что переменная, созданная разыменованием указателя, всегда классифицируется как фиксированная переменная.

18.4 Преобразования указателей

В небезопасном контексте набор доступных неявных преобразований (§6.1) расширен за счет включения следующих неявных преобразований указателей:

· от любого типа_указателя к типу void*.

· от литерала null к любому типу_указателя.

Кроме того, в небезопасном контексте набор доступных явных преобразований (§6.2) расширен за счет включения следующих явных преобразований указателей:

· от любого типа_указателя к любому другому типу_указателя;

· от типов sbyte, byte, short, ushort, int, uint, long и ulong к любому типу_указателя;

· от любого типа_указателя к типам sbyte, byte, short, ushort, int, uint, long и ulong.

И наконец, в небезопасном контексте набор стандартных неявных преобразований (§6.3.1) включает следующее преобразование указателя:

· от любого типа_указателя к типу void*.

Преобразования между двумя типами указателей никогда не изменяют фактическое значение указателя. Иначе говоря, преобразование от одного типа указателя к другому не влияет на основной адрес, задаваемый указателем.

При преобразовании одного типа указателя к другому, если полученный указатель неправильно выровнен для указываемого типа, поведение является неопределенным, если результат разыменован. В общем, понятие «правильно выровненный» является транзитивным: если указатель на тип A правильно выровнен для указателя на тип B, который в свою очередь правильно выровнен для указателя на тип C, то указатель на тип A правильно выровнен для указателя на тип C.

Рассмотрите следующий случай, когда доступ к переменной одного типа выполняется через указатель на другой тип:

char c = 'A';
char* pc = &c;
void* pv = pc;
int* pi = (int*)pv;
int i = *pi; // неопределенный
*pi = 123456; // неопределенный

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

using System;

class Test
{
unsafe static void Main() {
double d = 123.456e23;
unsafe {
byte* pb = (byte*)&d;
for (int i = 0; i < sizeof(double); ++i)
Console.Write("{0:X2} ", *pb++);
Console.WriteLine();
}
}
}

Конечно, производимый вывод зависит от порядка следования байтов.

Сопоставления между указателями и целыми определяются реализацией. Однако на архитектурах 32- и 64-разрядных ЦП с линейным адресным пространством преобразования указателей к целым типам и целых типов к указателям обычно происходит точно так же, как преобразования значений типа uint или ulong соответственно к этим целым типам или от них к указателям.

18.5 Указатели в выражениях

В небезопасном контексте выражение может давать результат типа указателя, но вне небезопасного контекста выражение с типом указателя вызывает ошибку времени компиляции. Говоря точно, ошибка времени компиляции вне небезопасного контекста вызывается в случае, когда любое простое_имя (§7.5.2), доступ_к_члену (§7.5.4), выражение_вызова (§7.5.5) или доступ_к_элементу (§7.5.6) имеет тип указателя.

В небезопасном контексте порождения первичного_выражения_создания_не_массива (§7.5) и унарного_выражения (§7.6) дают возможность создавать следующие дополнительные конструкции:

первичное_выражение_создания_не_массива:
...
доступ_к_члену_по_указателю
доступ_к_элементу_по_указателю
выражение_sizeof

унарное_выражение:
...
выражение_косвенного_обращения_по_указателю
выражение_адрес

Эти конструкции описаны в следующих разделах. Приоритет и ассоциативность небезопасных операторов подразумеваются грамматикой.

18.5.1 Косвенное обращение по указателю

Выражение_косвенного_обращения_по_указателю состоит из звездочки (*) с последующим унарным_выражением.

выражение_косвенного_обращения_по_указателю:
* унарное_выражение

Унарный оператор * означает косвенное обращение по указателю и используется для получения переменной, на которую указывает указатель. Результатом вычисления *P, где P — это выражение типа указателя T*, является переменная типа T. Применение унарного оператора * к выражению типа void* или к выражению не типа указателя является ошибкой времени компиляции.

Результат применения унарного оператора * к указателю со значением null определяется реализацией. В частности, нет гарантии, что по этой операции будет выдана исключительная ситуация System.NullReferenceException.

Если указателю присвоено недопустимое значение, поведение унарного оператора * является неопределенным. Среди недопустимых значений для разыменования указателя унарным оператором * неправильно выровненный адрес указываемого типа (см. пример в §18.4) и адрес переменной после окончания ее времени жизни.

Для целей анализа определенного присваивания переменная, полученная вычислением выражения вида *P, считается изначально присвоенной (§5.3.1).

18.5.2 Доступ к члену по указателю

Доступ_к_члену_по_указателю состоит из первичного_выражения, за которым следует лексема "->" и затем идентификатор.

доступ_к_члену_по_указателю:
первичное_выражение -> идентификатор

В доступе к члену по указателю вида P->I, P должно быть выражением типа указателя, отличного от void*, а I должно обозначать доступный член того типа, на который указывает P.

Доступ к члену по указателю вида P->I вычисляется точно так же, как (*P).I. Описание оператора косвенного обращения по указателю (*) см. в §18.5.1. Описание оператора доступа к члену (.) см. в §7.5.4.

В примере

using System;

struct Point
{
public int x;
public int y;

public override string ToString() {
return "(" + x + "," + y + ")";
}
}

class Test
{
static void Main() {
Point point;
unsafe {
Point* p = &point;
p->x = 10;
p->y = 20;
Console.WriteLine(p->ToString());
}
}
}

оператор -> используется для доступа к полям и вызова метода структуры с помощью указателя. Поскольку операция P->I совершенно эквивалентна (*P).I, метод Main можно было записать так:

class Test
{
static void Main() {
Point point;
unsafe {
Point* p = &point;
(*p).x = 10;
(*p).y = 20;
Console.WriteLine((*p).ToString());
}
}
}

18.5.3 Доступ к элементу по указателю

Доступ_к_элементу_по_указателю состоит из первичного_выражения_создания_не_массива, за которым следует выражение, заключенное в скобки «[" и "]».

доступ_к_элементу_по_указателю:
первичное_выражение_создания_не_массива [ выражение ]

В доступе к элементу по указателю вида P[E], P должно быть выражением типа указателя, отличного от void*, а E должно быть выражением такого типа, который может быть неявно преобразован к типу int, uint, long или ulong.

Доступ к элементу по указателю вида P[E] вычисляется точно так же, как *(P + E). Описание оператора косвенного обращения по указателю (*) см. в §18.5.1. Описание оператора добавления к указателю (+) см. в §18.5.6.

В примере

class Test
{
static void Main() {
unsafe {
char* p = stackalloc char[256];
for (int i = 0; i < 256; i++) p[i] = (char)i;
}
}
}

доступ к элементу по указателю используется для инициализации символьного буфера в цикле for. Поскольку операция P[E] совершенно эквивалентна *(P + E), этот пример можно было записать так:

class Test
{
static void Main() {
unsafe {
char* p = stackalloc char[256];
for (int i = 0; i < 256; i++) *(p + i) = (char)i;
}
}
}

Оператор доступа к элементу по указателю не проверяет на ошибки выхода за допустимые границы, и поведение при доступе к элементу вне границ является неопределенным. Это так же, как в C и C++.

18.5.4 Оператор адреса

Выражение_адрес состоит из амперсанда (&), за которым следует унарное_выражение.

выражение_адрес:
& унарное_выражение

Если дано выражение E типа T и классифицируемое как фиксированная переменная (§18.3), конструкция &E вычисляет адрес переменной, заданной выражением E. Типом результата является T*, классифицируемый как значение. Вызывается ошибка времени компиляции, если E не классифицируется как переменная, если E классифицируется как локальная переменная только для чтения или если E обозначает перемещаемую переменную. В этом последнем случае можно использовать оператор fixed (§18.6), чтобы временно «фиксировать» переменную перед получением ее адреса. Как изложено в §7.5.4, вне конструктора экземпляров или статического конструктора для структуры или класса, определяющего поле readonly, это поле считается значением, а не переменной. Его адрес, как таковой, нельзя получить. Аналогично и адрес константы получить нельзя.

Оператор & не требует, чтобы его аргументы были определенно присвоены, но после операции & та переменная, к которой применен этот оператор, считается определенно присвоенной на пути выполнения, на котором происходит эта операция. На программиста возлагается обязанность обеспечить, чтобы правильная инициализация переменной фактически происходила в такой ситуации.

В примере

using System;

class Test
{
static void Main() {
int i;
unsafe {
int* p = &i;
*p = 123;
}
Console.WriteLine(i);
}
}

i считается определенно присвоенным после операции &i, использованной для инициализации p. Присваивание для *p в сущности инициализирует i, но включение этой инициализации является обязанностью программиста, и если удалить это присваивание, ошибки времени компиляции не будет.

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

18.5.5 Увеличение и уменьшение указателя

В небезопасном контексте операторы ++ и ‑‑ (§7.5.9 и §7.6.5) могут применяться к переменным типа указателя всех типов, кроме void*. Так, для каждого типа указателя T* неявно определены следующие операторы:

T* оператор ++(T* x);

T* оператор --(T* x);

Эти операторы дают такой же результат, как x + 1 и x - 1, соответственно (§18.5.6). Иначе говоря, для переменной типа указателя с типом T* оператор ++ добавляет sizeof(T) к адресу, содержащемуся в переменной, а оператор ‑‑ вычитает sizeof(T) из адреса, содержащегося в переменной.

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

18.5.6 Арифметические операции с указателем

В небезопасном контексте операторы + и - (§7.7.4 и §7.7.5) могут применяться к значениям всех типов указателей, кроме void*. Так, для каждого типа указателя T* неявно определены следующие операторы:

T* оператор +(T* x, int y);
T* оператор +(T* x, uint y);
T* оператор +(T* x, long y);
T* оператор +(T* x, ulong y);

T* оператор +(int x, T* y);
T* оператор +(uint x, T* y);
T* оператор +(long x, T* y);
T* оператор +(ulong x, T* y);

T* оператор –(T* x, int y);
T* оператор –(T* x, uint y);
T* оператор –(T* x, long y);
T* оператор –(T* x, ulong y);

long оператор –(T* x, T* y);

Если дано выражение P типа указателя T* и выражение N типа int, uint, long или ulong, то выражения P + N и N + P вычисляют значение указателя типа T*, получающееся при добавлении N * sizeof(T) к адресу, заданному выражением P. Аналогично выражение P - N вычисляет значение указателя типа T*, получающееся при вычитании N * sizeof(T) из адреса, заданного выражением P.

Если даны два выражения, P и Q, типа указателя T*, то выражение P – Q вычисляет разность адресов, заданных P и Q, а затем делит эту разность на sizeof(T). Тип результата всегда long. В сущности, P - Q вычисляется как ((long)(P) - (long)(Q)) / sizeof(T).

Например:

using System;

class Test
{

static void Main() {
unsafe {
int* values = stackalloc int[20];
int* p = &values[1];
int* q = &values[15];
Console.WriteLine("p - q = {0}", p - q);
Console.WriteLine("q - p = {0}", q - p);
}
}
}

В результате получается:

p - q = -14
q - p = 14

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

18.5.7 Сравнение указателей

В небезопасном контексте операторы ==, !=, <, >, <= и => (§7.9) можно применять к значениям всех типов указателей. Операторы сравнения указателей следующие:

bool оператор ==(void* x, void* y);

bool оператор !=(void* x, void* y);

bool оператор <(void* x, void* y);

bool оператор >(void* x, void* y);

bool оператор <=(void* x, void* y);

bool оператор >=(void* x, void* y);

Поскольку существует неявное преобразование любого типа указателей к типу void*, операнды любого типа указателей можно сравнивать с помощью этих операторов. Операторы сравнения сравнивают адреса, заданные двумя операндами, как если бы они были беззнаковыми целыми.

Sizeof

Оператор sizeof возвращает число байт, занимаемых переменной данного типа. Тип, указанный в качестве операнда sizeof, должен быть неуправляемым_типом (§18.2).

выражение_sizeof:
sizeof ( неуправляемый_тип )

Результатом оператора sizeof является значение типа int. Для некоторых предопределенных типов оператор sizeof дает постоянное значение, как указано в приведенной ниже таблице.

 

Выражение Результат
sizeof(sbyte)
sizeof(byte)
sizeof(short)
sizeof(ushort)
sizeof(int)
sizeof(uint)
sizeof(long)
sizeof(ulong)
sizeof(char)
sizeof(float)
sizeof(double)
sizeof(bool)

 

Для всех других типов результат оператора sizeof определяется реализацией и классифицируется как значение, а не константа.

Порядок объединения членов в структуру не установлен.

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

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

Fixed

В небезопасном контексте порождение внедренного_оператора (§8) допускает дополнительную конструкцию, оператор fixed, используемый для «фиксирования» перемещаемой переменной, так что ее адрес остается постоянным на время действия этого оператора.

внедренный_оператор:
...
оператор_fixed

оператор_fixed:
fixed ( тип_указателя деклараторы_указателя_fixed ) внедренный_оператор

деклараторы_указателя_fixed:
декларатор_указателя_fixed
деклараторы_указателя_fixed , декларатор_указателя_fixed

декларатор_указателя_fixed:
идентификатор = инициализатор_указателя_fixed

инициализатор_указателя_fixed:
& ссылка_на_переменную
выражение

Каждый декларатор_указателя_fixed объявляет локальную переменную данного типа_указателя и инициализирует эту локальную переменную адресом, вычисленным соответствующим инициализатором_указателя_fixed. Локальная переменная, объявленная в операторе fixed, доступна в любом инициализаторе_указателя_fixed, находящемся справа от объявления этой переменной, и во внедренном_операторе оператора fixed. Локальная переменная, объявленная оператором fixed, считается доступной только для чтения. Возникает ошибка времени компиляции, если внедренный оператор пытается изменить эту локальную переменную (через присваивание или операторами ++ и ‑‑) или передать ее в качестве параметра ref или out.

Инициализатором_указателя_fixed может быть одно из следующего:

· лексема «&», за которой следует ссылка_на_переменную (§5.3.3) на перемещаемую переменную (§18.3) неуправляемого типа T при условии, что тип T* является неявно преобразуемым к типу указателя, заданному в операторе fixed. В этом случае инициализатор вычисляет адрес заданной переменной и эта переменная гарантированно остается по фиксированному адресу на время действия оператора fixed;

· выражение типа_массива с элементами неуправляемого типа T при условии, что тип T* является неявно преобразуемым к типу, заданному в операторе fixed. В этом случае инициализатор вычисляет адрес первого элемента массива, а весь массив гарантированно остается по фиксированному адресу на время действия оператора fixed. Поведение оператора fixed определяется реализацией, если выражение массива дает null или если в массиве нуль элементов;

· выражение типа string при условии, что тип char* является неявно преобразуемым к типу указателя, заданному в операторе fixed. В этом случае инициализатор вычисляет адрес первого символа строки, а вся строка гарантированно остается по фиксированному адресу на время действия оператора fixed. Поведение оператора fixed определяется реализацией, если строковое выражение дает null;

· простое_имя или доступ_к_члену, ссылающиеся на член буфера фиксированного объема перемещаемой переменной, при условии, что тип члена буфера фиксированного объема является неявно преобразуемым к типу указателя, заданному в операторе fixed. В этом случае инициализатор вычисляет указатель на первый элемент буфера фиксированного объема (§18.7.2), а буфер фиксированного объема гарантированно остается по фиксированному адресу на время действия оператора fixed.

Для каждого адреса, вычисленного инициализатором_указателя_fixed, оператор fixed гарантирует, что переменная, находящаяся по этому адресу, не подлежит перемещению или удалению сборщиком мусора на время действия оператора fixed. Например, если адрес, вычисленный инициализатором_указателя_fixed, ссылается на поле объекта или на элемент экземпляра массива, оператор fixed гарантирует, что содержащий экземпляр объекта не будет перемещен или удален в течение срока жизни этого оператора.

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

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

В примере

class Test
{
static int x;
int y;

unsafe static void F(int* p) {
*p = 1;
}

static void Main() {
Test t = new Test();
int[] a = new int[10];
unsafe {
fixed (int* p = &x) F(p);
fixed (int* p = &t.y) F(p);
fixed (int* p = &a[0]) F(p);
fixed (int* p = a) F(p);
}
}
}

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

Третий и четвертый операторы fixed в приведенном выше примере дают одинаковые результаты. Вообще, для экземпляра массива a указание &a[0] в операторе fixed — это то же самое, что просто указание a.

В этом примере оператора fixed используется тип string:

class Test
{
static string name = "xx";

unsafe static void F(char* p) {
for (int i = 0; p[i] != '\0'; ++i)
Console.WriteLine(p[i]);
}

static void Main() {
unsafe {
fixed (char* p = name) F(p);
fixed (char* p = "xx") F(p);
}
}
}

В небезопасном контексте элементы массива в одномерном массиве хранятся в порядке возрастания индекса, начиная с индекса 0 и заканчивая индексом Length – 1. В многомерных массивах элементы хранятся так, что сначала возрастают индексы самого правого измерения, затем соседнего левого и так далее влево. В пределах оператора fixed, получающего указатель p на экземпляр массива a, значения указателя в диапазоне от p до p + a.Length - 1 представляют адреса элементов массива. Аналогично переменные в диапазоне от p[0] до p[a.Length - 1] представляют фактические элементы массива. При данном способе хранения массивов можно обращаться с массивом любого измерения как если бы он был линейным.

Например:

using System;

class Test
{
static void Main() {
int[,,] a = new int[2,3,4];
unsafe {
fixed (int* p = a) {
for (int i = 0; i < a.Length; ++i) // обрабатывать как линейный
p[i] = i;
}
}

for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 4; ++k)
Console.Write("[{0},{1},{2}] = {3,2} ", i, j, k, a[i,j,k]);
Console.WriteLine();
}
}
}

В результате получается:

[0,0,0] = 0 [0,0,1] = 1 [0,0,2] = 2 [0,0,3] = 3
[0,1,0] = 4 [0,1,1] = 5 [0,1,2] = 6 [0,1,3] = 7
[0,2,0] = 8 [0,2,1] = 9 [0,2,2] = 10 [0,2,3] = 11
[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15
[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19
[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23

В примере

class Test
{
unsafe static void Fill(int* p, int count, int value) {
for (; count != 0; count--) *p++ = value;
}

static void Main() {
int[] a = new int[100];
unsafe {
fixed (int* p = a) Fill(p, 100, -1);
}
}
}

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

В следующем примере:

unsafe struct Font
{
public int размер;
public fixed char name[32];
}

class Test
{
unsafe static void PutString(string s, char* buffer, int bufSize) {
int len = s.Length;
if (len > bufSize) len = bufSize;
for (int i = 0; i < len; i++) buffer[i] = s[i];
for (int i = len; i < bufSize; i++) buffer[i] = (char)0;
}

Font f;

unsafe static void Main()
{
Test test = new Test();
test.f.size = 10;
fixed (char* p = test.f.name) {
PutString("Times New Roman", p, 32);
}
}
}

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

Значение char*, созданное фиксацией экземпляра строки, всегда указывает на строку, оканчивающуюся символом null. В пределах оператора fixed, получающего указатель p на экземпляр строки s, значения указателя в диапазоне от p до p + s.Length - 1 представляют адреса символов в строке, а значение указателя p + s.Length всегда указывает на символ null (символ со значением '\0').

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

Автоматическое завершение строк символом null особенно удобно при вызове внешних API, ожидающих строки «в стиле C». Отметьте, однако, что в экземпляре строки могут содержаться символы null. Если символы null имеются, то строка будет выглядеть усеченной, если обращаться с ней как с завершающейся символом null строкой char*.

18.7 Буферы фиксированного размера

Буферы фиксированного размера используются для объявления «в стиле C» линейных массивов как членов структур, главным образом они полезны для связи с неуправляемыми API.

18.7.1 Объявления буферов фиксированного размера

Буфер фиксированного размера является членом, представляющим хранилище для буфера фиксированной длины для переменных заданного типа. Объявление буфера фиксированного размера вводит один или более буферов фиксированного размера с заданным типом элементов. Буферы фиксированного размера допускаются только в объявлениях структур и могут быть только в небезопасных контекстах (§18.1).

объявление_члена_структуры:

объявление_буфера_фиксированного_размера

объявление_буфера_фиксированного_размера:
атрибутынеоб модификаторы_буфера_фиксированного_размеранеоб fixed тип_элемента_буфера
деклараторы-буфера-фиксированного-размера ;

модификаторы_буфера_фиксированного_размера:
модификатор_буфера_фиксированного_размера
модификатор_буфера_фиксированного_размера модификаторы_буфера_фиксированного_размера

модификатор_буфера_фиксированного_размера:
new
public
protected
internal
private
unsafe

тип_элемента_буфера:
тип

деклараторы_буфера_фиксированного_размера:
декларатор_буфера_фиксированного_размера
декларатор_буфера_фиксированного_размера деклараторы_буфера_фиксированного_размера

декларатор_буфера_фиксированного_размера:
идентификатор [ константное_выражение ]

Объявление буфера фиксированного размера может включать набор атрибутов (§17), модификатор new (§10.2.2), допустимое сочетание из четырех модификаторов доступа (§10.2.3) и модификатор unsafe (§18.1). Атрибуты и модификаторы применяются ко всем членам, объявленным в объявлении буфера фиксированного размера. Является ошибкой, если один и тот же модификатор появляется несколько раз в объявлении буфера фиксированного размера.

В объявление буфера фиксированного размера нельзя включать модификатор static.

Тип элемента буфера в объявлении буфера фиксированного размера указывает тип элемента буфера(-ов), введенных объявлением. Тип элемента буфера должен быть одним из предопределенных типов sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double или bool.

За типом элемента буфера следует список деклараторов буфера фиксированного размера, каждый из которых вводит новый член. Декларатор буфера фиксированного размера состоит из идентификатора, именующего член, за ним следует константное выражение, заключенное в лексемы [ и ]. Константное выражение указывает число элементов в члене, введенном этим декларатором буфера фиксированного размера. Тип константного выражения должен быть неявно преобразуемым к типу int, а значение должно быть ненулевым положительным целым числом.

Элементы буфера фиксированного размера гарантированно размещаются последовательно в памяти.

Объявление буфера фиксированного размера, в котором объявлено несколько буферов фиксированного размера, эквивалентно нескольким объявлениям с объявлением одного буфера фиксированного размера с теми же атрибутами и типами элементов. Например,

unsafe struct A
{
public fixed int x[5], y[10], z[100];
}

эквивалентно

unsafe struct A
{
public fixed int x[5];
public fixed int y[10];
public fixed int z[100];
}

18.7.2 Буферы фиксированного размера в выражениях

Поиск члена (§7.3) буфера фиксированного размера выполняется точно так же, как поиск поля.

На буфер фиксированного размера можно ссылаться в выражении с помощью простого_имени (§7.5.2) или доступа_к_члену (§7.5.4).

При ссылке на член буфера фиксированного размера по простому имени результат такой же, как при доступе к члену в виде this.I, где I — член буфера фиксированного размера.

При доступе к члену в виде E.I если E имеет тип структуры и поиск члена I в этом типе структуры идентифицирует член фиксированного размера, E.I вычисляется и классифицируется следующим образом:

· если выражение E.I встречается не в небезопасном контексте, выдается ошибка времени компиляции;

· если E классифицируется как значение, выдается ошибка времени компиляции;

· иначе, если E является перемещаемой переменной (§18.3), а выражение E.I не является инициализатором_указателя_fixed (§18.6), выдается ошибка времени компиляции;

· иначе E ссылается на фиксированную переменную, а результатом выражения является указатель на первый элемент члена I буфера фиксированного размера в E. Результат имеет тип S*, где S — тип элемента I, и классифицируется как значение.

Доступ к последующим элементам буфера фиксированного размера можно получить с помощью операций с указателем от первого элемента. В отличие от доступа к массивам, доступ к элементам буфера фиксированного размера является небезопасной операцией без проверки диапазона.

В следующем примере объявляется и используется структура с членом, являющимся буфером фиксированного размера.

unsafe struct Font
{
public int size;
public fixed char name[32];
}

class Test
{
unsafe static void PutString(string s, char* buffer, int bufSize) {
int len = s.Length;
if (len > bufSize) len = bufSize;
for (int i = 0; i < len; i++) buffer[i] = s[i];
for (int i = len; i < bufSize; i++) buffer[i] = (char)0;
}

unsafe static void Main()
{
Font f;
f.size = 10;
PutString("Times New Roman", f.name, 32);
}
}

18.7.3 Проверка определенного присваивания

Буферы фиксированного размера не подлежат проверке определенного присваивания (§5.3), а члены буфера фиксированного размера игнорируются при проверке определенного присваивания переменным типа структуры.

Если самая дальняя переменная типа структуры, включающая элемент буфера фиксированного размера, является статической переменной, переменной экземпляра экземпляра класса или элементом массива, элементы буфера фиксированного размера автоматически инициализируются своими значениями по умолчанию (§5.2). Во всех других случаях начальное содержимое буфера фиксированного размера является неопределенным.

18.8 Выделение стека

В небезопасном контексте объявление локальной переменной (§8.5.1) может включать инициализатор выделения стека, который выделяет память из стека вызова.

инициализатор_локальной_переменной:

инициализатор_stackalloc

инициализатор_stackalloc:
stackalloc неуправляемый_тип [ выражение ]

Неуправляемый_тип указывает тип элементов, которые будут сохраняться во вновь выделенном месте, а выражение указывает количество этих элементов. Совместно они указывают необходимый размер выделения. Так как размер выделения стека не может быть отрицательным, выдается ошибка времени компиляции, если число элементов задано константным_выражением, дающим в результате отрицательное значение.

Для инициализатора выделения стека вида stackalloc T[E] требуется, чтобы T было неуправляемого типа (§18.2), а E было выражением типа int. Эта конструкция выделяет E * sizeof(T) байт из стека вызова и возвращает указатель (типа T*) на вновь выделенный блок. Если E — отрицательное значение, то поведение неопределенное. Если E равно нулю, то выделение не производится, а возвращаемый указатель определяется реализацией. Если не хватает памяти для выделения блока заданного размера, выдается System.StackOverflowException.

Содержимое вновь выделенной памяти неопределенное.

Инициализаторы выделения стека не допускаются в блоках catch или finally (§8.10).

Не существует способ явного освобождения памяти, выделенной с помощью stackalloc. Все блоки выделенной стеку памяти, созданные во время выполнения члена функции, автоматически удаляются при возврате из члена функции. Это соответствует функции alloca, расширение, обычно встречающееся в реализациях C и C++.

В примере

using System;

class Test
{
static string IntToString(int value) {
int n = value >= 0? value: -value;
unsafe {
char* buffer = stackalloc char[16];
char* p = buffer + 16;
do {
*--p = (char)(n % 10 + '0');
n /= 10;
} while (n != 0);
if (value < 0) *--p = '-';
return new string(p, 0, (int)(buffer + 16 - p));
}
}

static void Main() {
Console.WriteLine(IntToString(12345));
Console.WriteLine(IntToString(-999));
}
}

инициализатор stackalloc используется в методе IntToString для выделения буфера размером 16 символов в стеке. Этот буфер автоматически удаляется при возврате метода.

18.9 Динамическое выделение памяти

Кроме оператора stackalloc, C# не предоставляет предопределенные конструкции для управления памятью, не собираемой сборщиком мусора. Такие службы обычно предоставляются поддержкой библиотек классов или импортируются непосредственно из операционной системы. В следующем примере показано в классе Memory, как можно обратиться к функциям кучи операционной системы из C#:

using System;
using System.Runtime.InteropServices;

public unsafe class Memory
{
// Дескриптор для кучи процесса. Этот дескриптор используется во всех вызовах
// API HeapXXX в методах внизу.

static int ph = GetProcessHeap();

// Конструктор экземпляра Private для предотвращения создания экземпляра.

private Memory() {}

// Выделяет блок памяти заданного размера. Выделенная память
// автоматически инициализируется нулями.

public static void* Alloc(int size) {
void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
if (result == null) throw new OutOfMemoryException();
return result;
}

// Копирует счетчик байт из src в dst. Блоки исходный и конечный
// могут перекрываться.

public static void Copy(void* src, void* dst, int count) {
byte* ps = (byte*)src;
byte* pd = (byte*)dst;
if (ps > pd) {
for (; count != 0; count--) *pd++ = *ps++;
}
else if (ps < pd) {
for (ps += count, pd += count; count != 0; count--) *--pd = *--ps;
}
}

// Освобождает блок памяти.

public static void Free(void* block) {
if (!HeapFree(ph, 0, block)) throw new InvalidOperationException();
}

// Перераспределяет блок памяти. Если запрос перераспределения на
// больший размер, дополнительная область памяти автоматически
// инициализируется нулями.

public static void* ReAlloc(void* block, int size) {
void* result = HeapReAlloc(ph, HEAP_ZERO_MEMORY, block, size);
if (result == null) throw new OutOfMemoryException();
return result;
}

// Возвращает размер блока памяти.

public static int SizeOf(void* block) {
int result = HeapSize(ph, 0, block);
if (result == -1) throw new InvalidOperationException();
return result;
}

// Флаги API кучи

const int HEAP_ZERO_MEMORY = 0x00000008;

// Функции API кучи

[DllImport("kernel32")]
static extern int GetProcessHeap();

[DllImport("kernel32")]
static extern void* HeapAlloc(int hHeap, int flags, int size);

[DllImport("kernel32")]
static extern bool HeapFree(int hHeap, int flags, void* block);

[DllImport("kernel32")]
static extern void* HeapReAlloc(int hHeap, int flags,
void* block, int size);

[DllImport("kernel32")]
static extern int HeapSize(int hHeap, int flags, void* block);
}

Пример использования класса Memory:

class Test
{
static void Main() {
unsafe {
byte* buffer = (byte*)Memory.Alloc(256);
try {
for (int i = 0; i < 256; i++) buffer[i] = (byte)i;
byte[] array = new byte[256];
fixed (byte* p = array) Memory.Copy(buffer, p, 256);
}
finally {
Memory.Free(buffer);
}
for (int i = 0; i < 256; i++) Console.WriteLine(array[i]);
}
}
}

В этом примере выделяется 256 байт памяти через Memory.Alloc и блок памяти инициализируется значениями, возрастающими от 0 до 255. Затем размещается байтовый массив из 256 элементов и используется Memory.Copy для копирования содержимого блока памяти в байтовый массив. Наконец, этот блок памяти освобождается с помощью Memory.Free и содержимое байтового массива выводится на консоль.


A. Комментарии к документации

C# предоставляет программистам механизм документирования своего кода с помощью специального синтаксиса комментариев с XML-текстом. Комментарии в файлах исходного кода, имеющие определенный вид, могут быть использованы для управления инструментом создания XML из этих комментариев и элементов исходного кода, которым они предшествуют. Комментарии, использующие такой синтаксис, называются комментариями к документации. Они должны непосредственно предшествовать пользовательскому типу (такому как класс, делегат или интерфейс) или члену (такому как поле, событие, свойство или метод). Инструмент создания XML называется генератором документации. (Таким генератором может быть, но не обязан, сам компилятор C#.) Производимый генератором документации вывод называется файлом документации. Файл документации используется в качестве входных данных для средства просмотра документации, инструмента для создания отображения сведений о типе и сопутствующей документации.

Эта спецификация предлагает набор тегов для использования в комментариях к документации, но использование этих тегов не является обязательным, можно при желании использовать другие теги, если соблюдаются правила правильного XML.

A.1 Введение

Комментарии, имеющие специальный вид, могут быть использованы для управления инструментом создания XML из этих комментариев и элементов исходного кода, которым они предшествуют. Такие комментарии являются однострочными комментариями, начинающимися с трех косых черт (///), или комментариями с разделителями, начинающимися с косой черты и двух звездочек (/**). Они должны непосредственно предшествовать пользовательскому типу (такому как класс, делегат или интерфейс) или члену (такому как поле, событие, свойство или метод), который они комментируют. Разделы атрибутов (§17.2) считаются частью объявлений, так что комментарии к документации должны предшествовать атрибутам, примененным к типу или члену.

Синтаксис:

однострочный_комментарий_к_документации:
/// символы_вводанеоб

комментарий_с_разделителями_к_документации:
/** символы_комментария_с_разделителяминеоб */

Если в однострочном_комментарии_к_документации символ пустого_пространства следует за символами /// в каждом из однострочных_комментариев_к_документации, примыкающих к текущему однострочному_комментарию_к_документации, то этот символ пустого_пространства не включается в XML-вывод.

Если в комментарии_с_разделителями_к_документации первый символ на второй строке, не являющийся символом пустого_пространства, является звездочкой, и тот же порядок необязательных символов пустого_пространства и символа звездочки повторяется в начале каждой строки внутри комментария_с_разделителями_к_документации, то эта повторяющаяся комбинация символов не включается в XML-вывод. Символы пустого_пространства могут входить в эту комбинацию как до, так и после символа звездочки.

Пример.

/// <summary>Класс <c>Point</c> моделирует точку в двумерной
/// плоскости.</summary>
///
public class Point
{
/// <summary>метод <c>draw</c> изображает точку.</summary>
void draw() {…}
}

Текст в комментариях к документации должен быть правильным согласно правилам XML (http://www.w3.org/TR/REC-xml). При неправильном XML создается предупреждение и файл документации будет содержать комментарий, сообщающий об ошибке.

Хотя разработчики могут создавать свои собственные наборы тегов, рекомендуемый набор определен в §A.2. Некоторые из рекомендуемых тегов имеют специальные значения:

· тег <param> используется для описания параметров. Если используется этот тег, генератор документации должен проверить, что указанный параметр существует и что все параметры описаны в комментариях к документации. Если проверка неуспешна, генератор документации выдает предупреждение.

· attribute can be attached to any tag to provide a reference to a code element. можно вложить в любой тег, чтобы предоставить ссылку на элемент кода. Генератор документации должен проверить, что этот элемент кода существует. Если проверка неуспешна, генератор документации выдает предупреждение. При поиске имени, описанного в атрибуте cref, генератор документации должен принимать во внимание видимость пространства имен в соответствии с операторами using, встречающимися в исходном коде. Для общих элементов кода нельзя использовать обычный универсальный синтаксис (т. е. "List<T>"), так как он создает неправильный XML. Можно использовать фигурные скобки вместо угловых (т. е. "List{T}") или escape-синтаксис XML (т. е. "List&lt;T&gt;").

· тег <summary> предназначен для использования средством просмотра документации для отображения дополнительных сведений о типе или члене.

· тег <include> включает сведения из внешнего XML-файла.

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

A.2 Рекомендуемые теги

Генератор документации обязан принять и обработать любой тег, допустимый по правилам XML. Следующие теги предоставляют обычно используемую функциональность в пользовательской документации. (Конечно, возможны и другие теги.)

 

Тег Раздел Назначение
<c> A.2.1 Установить шрифт текста, подобный исходному коду
<code> A.2.2 Установить одну или несколько строк исходного кода или программного вывода
<example> A.2.3 Указать пример
<exception> A.2.4 Идентификация исключений, которые могут выдаваться методом
<include> A.2.5 Включение XML из внешнего файла
<list> A.2.6 Создание списка или таблицы
<para> A.2.7 Разрешить добавление структуры к тексту
<param> A.2.8 Описать параметр для метода или конструктора
<paramref> A.2.9 Указать, что слово является именем параметра
<permission> A.2.10 Документировать специальные возможности безопасности члена
<summary> A.2.11 Описать тип
<returns> A.2.12 Описать возвращаемое методом значение
<see> A.2.13 Указать ссылку
<seealso> A.2.14 Создать запись See Also
<summary> A.2.15 Описать член типа
<value> A.2.16 Описать свойство
<typeparam>   Описать параметр универсального типа
<typeparamref>   Указать, что слово является именем параметра типа

 

A.2.1 Тег <c>

Этот тег предоставляет механизм для указания, что для фрагмента текста внутри описания следует установить особый шрифт, такой как используемый для блока кода. Для строк фактического текста используйте тег <code> (§A.2.2).

Синтаксис:

<c>текст</c>

Пример.

/// <summary>Класс <c>Point</c> моделирует точку в двумерной
/// плоскости.</summary>

public class Point
{
// ...
}

A.2.2 Тег <code>

Этот тег используется для установки для одной или нескольких строк исходного кода или программного вывода особого шрифта. Для маленьких фрагментов кода в комментарии используйте <c> (§A.2.1).

Синтаксис:

<code>исходный код или программный вывод</code>

Пример.

/// <summary>Этот метод изменяет положение точки
/// на заданное смещение x и y.
/// <example>Например:
/// <code>
/// Point p = new Point(3,5);
/// p.Translate(-1,3);
/// </code>
/// дает в результате <c>p</c> со значением (2,8).
/// </example>
/// </summary>

public void Translate(int xor, int yor) {
X += xor;
Y += yor;
}

A.2.3 Тег <example>

Этот тег позволяет вставить пример кода в комментарий, чтобы указать, как можно использовать метод или другой член библиотеки. Обычно это влечет также и использование тега <code> (§A.2.2).

Синтаксис:

<example>описание</example>

Пример.

См. пример в <code> (§A.2.2).

A.2.4 Тег <exception>

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

Синтаксис:

<exception cref="член">описание</exception>

где

cref="член"

Имя члена. Генератор документации проверяет, что данный член существует, и преобразует член в каноническое имя элемента в файле документации.

описание

Описание обстоятельств, при которых выдается это исключение.

Пример.

public class DataBaseOperations
{
/// <exception cref="MasterFileFormatCorruptException"></exception>
/// <exception cref="MasterFileLockedOpenException"></exception>
public static void ReadRecord(int flag) {
if (flag == 1)
throw new MasterFileFormatCorruptException();
else if (flag == 2)
throw new MasterFileLockedOpenException();
// …
}
}

A.2.5 Тег <include>

Этот тег позволяет включать сведения из внешнего XML-документа в файл исходного кода. Внешний файл должен быть правильным XML-документом, а выражение XPath применяется к этому документу для указания, какой XML из этого документа требуется включить. Затем тег <include> заменяется выбранным XML из внешнего документа.

Синтаксис:

<include file="имя_файла" path="xpath" />

где

file="имя_файла"

Имя внешнего XML-файла. Имя файла интерпретируется относительно файла, содержащего тег включения.

path="xpath"

Выражение Xpath, выбирающее один из XML во внешнем XML-файле.

Пример.

Если в исходном коде содержится такое объявление:

/// <include file="docs.xml" path='extradoc/class[@name="IntList"]/*' />
public class IntList { … }

а во внешнем файле "docs.xml" есть следующее содержимое:

<?xml version="1.0"?>
<extradoc>
<class name=
"IntList">
<summary>
Содержит список целых чисел.
</summary>
</class>
<class name=
"StringList">
<summary>
Содержит список целых чисел.
</summary>
</
class>
</
extradoc>

тогда эта же документация выводится, как если бы исходный код содержал:

/// <summary>
/// Содержит список целых чисел.
/// </
summary>
public class IntList { … }

A.2.6 Тег <list>

Этот тег используется для создания списка или таблицы элементов. Он может содержать блок <listheader> для определения строки заголовка таблицы или списка определений. (При определении таблицы необходимо предоставить только запись для термин в заголовке.)

Каждый элемент списка задается блоком <item>. При создании списка определений необходимо задать и термин, и описание. Но для таблицы, маркированного списка или нумерованного списка необходимо задать только описание.

Синтаксис:

<list type="bullet" | "number" | "table">
<listheader>
<term>термин</term>
<description>описание</description>
</listheader>
<item>
<term>термин</term>
<description>описание</description>
</item>

<item>
<term>термин</term>
<description>описание</description>
</item>
</list>

где

термин

определяемый термин, определенный в описание.

описание

либо элемент маркированного или нумерованного списка, либо определение термина.

Пример.

public class MyClass
{
/// <summary>Пример маркированного списка:
/// <list type="bullet">
/// <item>
/// <description>Элемент 1.</description>
/// </item>
/// <item>
/// <description>Элемент 2.</description>
/// </item>
/// </list>
/// </summary>
public static void Main () {
// ...
}
}

A.2.7 Тег <para>

Этот тег используется внутри других тегов, таких как <summary> (§A.2.11) или <returns> (§A.2.12), и позволяет добавить структуру к тексту.

Синтаксис:

<para>содержимое</para>

где

содержимое

текст абзаца.

Пример.

/// <summary>Это точка входа программы тестирования класса Point.
/// <para>Эта программа тестирует каждый метод и оператор
/// и ее предполагается выполнять после каждого нетривиального
/// обслуживания класса Point.</para></summary>
public static void Main() {
// ...
}

A.2.8 Тег <param>

Этот тег используется для описания параметра метода, конструктора или индексатора.

Синтаксис:

<param name="имя">описание</param>

где

имя

имя параметра.

описание

описание параметра.

Пример.

/// <summary>Этот метод изменяет положение точки
/// на заданные координаты.</summary>
/// <param name="xor">новая координата x.</param>
/// <param name="yor">новая координата y.</param>
public void Move(int xor, int yor) {
X = xor;
Y = yor;
}

A.2.9 Тег <paramref>

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

Синтаксис:

<paramref name="имя"/>

где

имя

имя параметра.

Пример.

/// <summary>Этот конструктор инициализирует новый Point с параметрами
/// (<paramref name="xor"/>,<paramref name="yor"/>).</summary>
/// <param name="xor">новая координата x точки.</param>
/// <param name="yor">новая координата y точки.</param>

public Point(int xor, int yor) {
X = xor;
Y = yor;
}

A.2.10 Тег <permission>

Этот тег дает возможность документировать специальные возможности безопасности члена.

Синтаксис:

<permission cref="член">описание</permission>

где

cref="член"

имя члена. Генератор документации проверяет, что данный элемент кода существует, и преобразует член в каноническое имя элемента в файле документации.

описание

описание доступа к члену.

Пример.

/// <permission cref="System.Security.PermissionSet">Все могут
/// получить доступ к этому методу.</permission>

public static void Test() {
// ...
}

A.2.11 Тег <summary>

Этот тег используется для указания общих сведений о типе. (Используйте <summary> (§A.2.15) для описания членов типа.)

Синтаксис:

<summary>описание</summary>

где

описание

текст общих сведений.

Пример.

/// <summary>Класс <c>Point</c> моделирует точку
/// в двумерной плоскости.</summary>
///
public class Point
{
// ...
}

A.2.12 Тег <returns>

Этот тег используется для описания возвращаемого методом значения.

Синтаксис:

<returns>описание</returns>

где

описание

описание возвращаемого значения.

Пример.

/// <summary>Сообщает положение точки в виде строки.</summary>
/// <returns>Строка, представляющая положение точки в виде (x,y),
/// без пустого пространства в начале, конце или внутри.</returns>
public override string ToString() {
return "(" + X + "," + Y + ")";
}

A.2.13 Тег <see>

Этот тег дает возможность указать ссылку внутри текста. Используйте <seealso> (§A.2.14) для указания текста, который должен быть представлен в разделе See Also.

Синтаксис:

<see cref="член"/>

где

cref="член"

имя члена. Генератор документации проверяет, что данный элемент кода существует, и заменяет член на имя элемента в сгенерированном файле документации.

Пример.

/// <summary>Этот метод изменяет положение точки
/// на заданные координаты.</summary>
/// <see cref="Translate"/>
public void Move(int xor, int yor) {
X = xor;
Y = yor;
}

/// <summary>Этот метод изменяет положение точки
/// на заданное смещение x и y.
/// </summary>
/// <see cref="Move"/>
public void Translate(int xor, int yor) {
X += xor;
Y += yor;
}

A.2.14 Тег <seealso>

Этот тег дает возможность создания записи для раздела See Also. Используйте <see> (§A.2.13) для указания ссылки изнутри текста.

Синтаксис:

<seealso cref="член"/>

где

cref="член"

имя члена. Генератор документации проверяет, что данный элемент кода существует, и заменяет член на имя элемента в сгенерированном файле документации.

Пример.

/// <summary>Этот метод определяет, находятся ли две точки в одном
/// месте.</summary>
/// <seealso cref="operator=="/>
/// <seealso cref="operator!="/>
public override bool Equals(object o) {
// ...
}

A.2.15 Тег <summary>

Этот тег используется для описания члена типа. Используйте <summary> (§A.2.11) для описания самого типа.

Синтаксис:

<summary>описание</summary>

где

описание

общие сведения о члене.

Пример.

/// <summary>Этот конструктор инициализирует новый Point значениями (0,0).</summary>
public Point() : this(0,0) {
}

A.2.16 Тег <value>

Этот тег дает возможность описать свойство.

Синтаксис:

<value>описание свойства</value>

где

описание свойства

описание свойства.

Пример.

/// <value>Свойство <c>X</c> представляет координату x точки.</value>
public int X
{
get { return x; }
set { x = value; }
}

A.2.17 Тег <typeparam>

Этот тег используется для описания параметра универсального типа для класса, структуры, интерфейса, делегата и метода.

Синтаксис:

<typeparam name="имя">описание</typeparam>

где

имя

имя параметра типа.

описание

описание параметра типа.

Пример.

/// <summary>Универсальный класс списка.</summary>
/// <typeparam name="T">Тип, сохраненный списком.</typeparam>
public class MyList<T> {
...
}

A.2.18 Тег <typeparamref>

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

Синтаксис:

<typeparamref name="имя"/>

где

имя

имя параметра типа.

Пример.

/// <summary>Этот метод выбирает данные и возвращает список <typeparamref name=”T”> ”/>”> .</summary>
/// <param name="string">запрос для выполнения</param>

public List<T> FetchData<T>(string query) {
...
}

A.3 Обработка файла документации

Генератор документации создает строку идентификатора для каждого элемента исходного кода, помеченного комментарием к документации. Эта строка идентификатора однозначно идентифицирует элемент источника. Средство просмотра документации может использовать строку идентификатора для идентификации соответствующего элемента метаданных или отражения, к которому применяется документация.

Файл документации не является иерархическим представлением исходного кода, это, скорее, единообразный список с созданной для каждого элемента строкой идентификатора.

A.3.1 Формат строки идентификатора

При создании строк идентификаторов генератор документации соблюдает следующие правила:

· в строку не заносятся пробелы;

· первая часть строки идентифицирует вид документируемого члена с помощью одного символа, за которым следует двоеточие. Определены следующие виды членов:

 

Символ Описание
E Событие
F Поле
M Метод (включая конструкторы, деструкторы и операторы)
N Пространство имен
P Свойство (включая индексаторы)
T Тип (такой как класс, делегат, перечисление, интерфейс или структура)
! Строка ошибки; в остальной части строки приведены сведения об ошибке. Например, генератор документации создает сведения об ошибке для ссылок, которые не удается разрешить.

 

· во второй части строки находится полное имя элемента, начиная от корня пространства имен. Имена элемента, вмещающих его типов и пространства имен разделены точками. Если в самом имени есть точки, они заменяются символами # (U+0023). (Предполагается, что в именах элементов нет такого символа);

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

o аргументы представлены своими именами в документации, основанными на их полном имени, со следующими изменениями:

к аргументам, представляющим универсальные типы, присоединяется символ «’», за которым следует число параметров типа;

у аргументов с модификатором out или ref за именем типа следует @. Аргументы, передаваемые по значению или через params, не имеют специального обозначения;

аргументы, являющиеся массивами, представляются как [ нижняя_граница : размер , … , нижняя_граница : размер ], где число запятых равно рангу минус один, а нижние границы и размер каждого измерения, если они известны, представляются десятичными числами. Если нижняя граница или размер не указаны, они опускаются. Если для отдельного измерения опущены и нижняя граница, и размер, то «:» тоже опускается. Массивы массивов представлены одним «[]» на каждый уровень:

аргументы, имеющие типы указателей, отличные от void, представляются с помощью * вслед за именем типа. Указатель типа void представляется с помощью имени типа System.Void;

аргументы, ссылающиеся на параметры универсального типа, определенные для типов, кодируются с помощью символа «`», за которым следует начинающийся с нуля индекс параметра типа;

аргументы, использующие параметры универсального типа, определенные в методах, используют двойной обратный апостроф «``» вместо одинарного «`», используемого для типов;

аргументы, ссылающиеся на построенные универсальные типы, кодируются с помощью универсального типа, за которым следует «{», затем разделенный запятыми список аргументов типа и затем «}».

A.3.2 Примеры строк идентификаторов

В каждом из следующих примеров показан фрагмент кода на C# вместе со строками идентификаторов, созданных по каждому исходному элементу, для которого возможен комментарий к документации:

· типы представлены своими полными именами, дополненными общими сведениями:

enum Color { Red, Blue, Green }

namespace Acme
{
interface IProcess {...}

struct ValueType {...}

class Widget: IProcess
{
public class NestedClass {...}

public interface IMenuItem {...}

public delegate void Del(int i);

public enum Direction { North, South, East, West }
}

class MyList<T>
{
class Helper<U,V> {...}
}
}

"T:Color"
"T:Acme.IProcess"
"T:Acme.ValueType"
"T:Acme.Widget"
"T:Acme.Widget.NestedClass"
"T:Acme.Widget.IMenuItem"
"T:Acme.Widget.Del"
"T:Acme.Widget.Direction"
”T:Acme.MyList`1”
”T:Acme.MyList`1.Helper`2”

· поля представлены своими полными именами:

namespace Acme
{
struct ValueType
{
private int total;
}

class Widget: IProcess
{
public class NestedClass
{
private int value;
}

private string message;
private static Color defaultColor;
private const double PI = 3.14159;
protected readonly double monthlyAverage;
private long[] array1;
private Widget[,] array2;
private unsafe int *pCount;
private unsafe float **ppValues;
}
}

"F:Acme.ValueType.total"
"F:Acme.Widget.NestedClass.value"
"F:Acme.Widget.message"
"F:Acme.Widget.defaultColor"
"F:Acme.Widget.PI"
"F:Acme.Widget.monthlyAverage"
"F:Acme.Widget.array1"
"F:Acme.Widget.array2"
"F:Acme.Widget.pCount"
"F:Acme.Widget.ppValues"

· конструкторы;

namespace Acme
{
class Widget: IProcess
{
static Widget() {...}

public Widget() {...}

public Widget(string s) {...}
}
}

"M:Acme.Widget.#cctor"
"M:Acme.Widget.#ctor"
"M:Acme.Widget.#ctor(System.String)"

· деструкторы;

namespace Acme
{
class Widget: IProcess
{
~Widget() {...}
}
}

"M:Acme.Widget.Finalize"

· методы;

namespace Acme
{
struct ValueType
{
public void M(int i) {...}
}

class Widget: IProcess
{
public class NestedClass
{
public void M(int i) {...}
}

public static void M0() {...}
public void M1(char c, out float f, ref ValueType v) {...}
public void M2(short[] x1, int[,] x2, long[][] x3) {...}
public void M3(long[][] x3, Widget[][,,] x4) {...}
public unsafe void M4(char *pc, Color **pf) {...}
public unsafe void M5(void *pv, double *[][,] pd) {...}
public void M6(int i, params object[] args) {...}
}

class MyList<T>
{
public void Test(T t) { }
}

class UseList
{
public void Process(MyList<int> list) { }
public MyList<T> GetValues<T>(T inputValue) { return null; }
}
}

"M:Acme.ValueType.M(System.Int32)"
"M:Acme.Widget.NestedClass.M(System.Int32)"
"M:Acme.Widget.M0"
"M:Acme.Widget.M1(System.Char,System.Single@,Acme.ValueType@)"
"M:Acme.Widget.M2(System.Int16[],System.Int32[0:,0:],System.Int64[][])"
"M:Acme.Widget.M3(System.Int64[][],Acme.Widget[0:,0:,0:][])"
"M:Acme.Widget.M4(System.Char*,Color**)"
"M:Acme.Widget.M5(System.Void*,System.Double*[0:,0:][])"
"M:Acme.Widget.M6(System.Int32,System.Object[])"
”M:Acme.MyList`1.Test(`0)”
”M:Acme.UseList.Process(Acme.MyList{System.Int32})”
”M:Acme.UseList.GetValues``(``0)”

· свойства и индексаторы;

namespace Acme
{
class Widget: IProcess
{
public int Width { get {...} set {...} }
public int this[int i] { get {...} set {...} }
public int this[string s, int i] { get {...} set {...} }
}
}

"P:Acme.Widget.Width"
"P:Acme.Widget.Item(System.Int32)"
"P:Acme.Widget.Item(System.String,System.Int32)"

· события;

namespace Acme
{
class Widget: IProcess
{
public event Del AnEvent;
}
}

"E:Acme.Widget.AnEvent"

· унарные операторы;

namespace Acme
{
class Widget: IProcess
{
public static Widget operator+(Widget x) {...}
}
}

"M:Acme.Widget.op_UnaryPlus(Acme.Widget)"

Полный набор имен функций унарных операторов следующий: op_UnaryPlus, op_UnaryNegation, op_LogicalNot, op_OnesComplement, op_Increment, op_Decrement, op_True и op_False.

· двоичные операторы;

namespace Acme
{
class Widget: IProcess
{
public static Widget operator+(Widget x1, Widget x2) {...}
}
}

"M:Acme.Widget.op_Addition(Acme.Widget,Acme.Widget)"

Полный набор имен функций двоичных операторов следующий: op_Addition, op_Subtraction, op_Multiply, op_Division, op_Modulus, op_BitwiseAnd, op_BitwiseOr, op_ExclusiveOr, op_LeftShift, op_RightShift, op_Equality, op_Inequality, op_LessThan, op_LessThanOrEqual, op_GreaterThan и op_GreaterThanOrEqual.

· операторы преобразования оканчиваются символом "~", за которым следует тип возвращаемого значения;

namespace Acme
{
class Widget: IProcess
{
public static explicit operator int(Widget x) {...}
public static implicit operator long(Widget x) {...}
}
}

"M:Acme.Widget.op_Explicit(Acme.Widget)~System.Int32"
"M:Acme.Widget.op_Implicit(Acme.Widget)~System.Int64"

A.4 Пример

A.4.1 Исходный код C#

В следующем примере показан исходный код класса Point:

namespace Graphics
{

/// <summary>Класс <c>Point</c> моделирует точку на двумерной плоскости.
/// </summary>
public class Point
{

/// <summary>Переменная экземпляра <c>x</c> представляет
/// координату x точки.</summary>
private int x;

/// <summary>Переменная экземпляра <c>y</c> представляет
/// координату y точки.</summary>
private int y;

/// <value>Свойство <c>X</c> представляет координату x точки.</value>
public int X
{
get { return x; }
set { x = value; }
}

/// <value>Свойство <c>Y</c> представляет координату y точки.</value>
public int Y
{
get { return y; }
set { y = value; }
}

/// <summary>Этот конструктор инициализирует новый Point значением
/// (0,0).</summary>
public Point() : this(0,0) {}

/// <summary>Этот конструктор инициализирует новый Point параметрами
/// (<paramref name="xor"/>,<paramref name="yor"/>).</summary>
/// <param><c>xor</c> — это координата x нового Point.</param>
/// <param><c>xor</c> — это координата y нового Point.</param>
public Point(int xor, int yor) {
X = xor;
Y = yor;
}

/// <summary>Этот метод устанавливает для точки
/// заданные координаты.</summary>
/// <param><c>xor</c> это новая координата x.</param>
/// <param><c>yor</c> это новая координата y.</param>
/// <see cref="Translate"/>
public void Move(int xor, int yor) {
X = xor;
Y = yor;
}

/// <summary>Этот метод перемещает точку
/// на заданное смещение x и y.
/// <example>Например:
/// <code>
/// Point p = new Point(3,5);
/// p.Translate(-1,3);
/// </code>
/// дает в результате <c>p</c> со значением (2,8).
/// </example>
/// </summary>
/// <param><c>xor</c> это относительное смещение x.</param>
/// <param><c>yor</c> это относительное смещение y.</param>
/// <see cref="Move"/>
public void Translate(int xor, int yor) {
X += xor;
Y += yor;
}

/// <summary>Этот метод определяет, находятся ли два объекта Point в одном
/// месте.</summary>
/// <param><c>o</c> — это объект, который требуется сравнить с текущим объектом.
/// </param>
/// <returns>True, если у объектов Point одинаковое расположение и
/// одинаковый тип; иначе False.</returns>
/// <seealso cref="operator=="/>
/// <seealso cref="operator!="/>
public override bool Equals(object o) {
if (o == null) {
return false;
}

if (this == o) {
return true;
}

if (GetType() == o.GetType()) {
Point p = (Point)o;
return (X == p.X) && (Y == p.Y);
}
return false;
}

/// <summary>Сообщить положение точки в виде строки.</summary>
/// <returns>Строка, представляющая положение точки в виде (x,y),
/// без пустого пространства в начале, конце или внутри.</returns>
public override string ToString() {
return "(" + X + "," + Y + ")";
}

/// <summary>Этот оператор определяет, находятся ли два объекта Point в одном
/// месте.</summary>
/// <param><c>p1</c> — это первый сравниваемый объект Point.</param>
/// <param><c>p2</c> это второй сравниваемый объект Point.</param>
/// <returns>True, если у объектов Point одинаковое расположение и
/// одинаковый тип; иначе False.</returns>
/// <seealso cref="Equals"/>
/// <seealso cref="operator!="/>
public static bool operator==(Point p1, Point p2) {
if ((object)p1 == null || (object)p2 == null) {
return false;
}

if (p1.GetType() == p2.GetType()) {
return (p1.X == p2.X) && (p1.Y == p2.Y);
}

return false;
}

/// <summary>Этот оператор определяет, находятся ли два объекта Point в одном
/// месте.</summary>
/// <param><c>p1</c> — это первый сравниваемый объект Point.</param>
/// <param><c>p2</c> это второй сравниваемый объект Point.</param>
/// <returns>True, если у объектов Point одинаковое расположение и
/// одинаковый тип; иначе False.</returns>
/// <seealso cref="Equals"/>
/// <seealso cref="operator=="/>
public static bool operator!=(Point p1, Point p2) {
return !(p1 == p2);
}

/// <summary>Это точка входа тестирующей класс Point
/// программы.
/// <para>Эта программа тестирует каждый метод и оператор и
/// предназначена для выполнения после каждого нетривиального изменения,
/// выполненного в классе Point.</para></summary>
public static void Main () {
// место для кода теста
}
}
}

A.4.2 Результирующий XML

Здесь представлен результат, созданный генератором документации по вышеприведенному исходному коду для класса Point:

<?xml version="1.0"?>
<doc>
<assembly>
<name>Point</name>
</assembly>
<members>
<member name="T:Graphics.Point">
<summary>Класс <c>Point</c> моделирует точку в двумерной
плоскости.
</summary>
</member>

<member name="F:Graphics.Point.x">
<summary>Переменная экземпляра <c>x</c> представляет
координату x точки.</summary>
</member>

<member name="F:Graphics.Point.y">
<summary>Переменная экземпляра <c>y</c> представляет
координату y точки.</summary>
</member>

<member name="M:Graphics.Point.#ctor">
<summary>Этот конструктор инициализирует новый Point значениями
(0,0).</summary>
</member>

<member name="M:Graphics.Point.#ctor(System.Int32,System.Int32)">
<summary>Этот конструктор инициализирует новый Point параметрами
(<paramref name="xor"/>,<paramref name="yor"/>).</summary>
<param><c>xor</c> — это координата x нового Point.</param>
<param><c>yor</c> — это координата y нового Point.</param>
</member>

<member name="M:Graphics.Point.Move(System.Int32,System.Int32)">
<summary>Этот метод устанавливает для точки
заданные координаты.</summary>
<param><c>xor</c> — это новая координата x.</param>
<param><c>yor</c> — это новая координата y.</param>
<see cref="M:Graphics.Point.Translate(System.Int32,System.Int32)"/>
</member>

<member
name="M:Graphics.Point.Translate(System.Int32,System.Int32)">
<summary>Этот метод изменяет положение точки
на заданное смещение x и y.
<example>Например:
<code>
Point p = new Point(3,5);
p.Translate(-1,3);
</code>
дает в результате <c>p</c> со значением (2,8).
</example>
</summary>
<param><c>xor</c> — это относительное смещение x.</param>
<param><c>yor</c> — это относительное смещение y.</param>
<see cref="M:Graphics.Point.Move(System.Int32,System.Int32)"/>
</member>

<member name="M:Graphics.Point.Equals(System.Object)">
<summary>Этот метод определяет, находятся ли два объекта Point в одном
месте.</summary>
<param><c>o</c> — это объект, который требуется сравнить с текущим
объектом.
</param>
<returns>True, если у объектов Point одинаковое расположение и
одинаковый тип; иначе False.</returns>
<seealso
cref="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)"/>
<seealso
cref="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"/>
</member>

<member name="M:Graphics.Point.ToString">
<summary>Сообщить положение точки в виде строки.</summary>
<returns>Строка, представляющая положение точки в виде
(x,y),
без пустого пространства в начале, конце или внутри.</returns>
</member>

<member
name="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)">
<summary>Этот оператор определяет, находятся ли два объекта Point в
одном
расположении.</summary>
<param><c>p1</c> — это первый сравниваемый объект Point.</param>
<param><c>p2</c> — это второй сравниваемый объект Point.</param>
<returns>True, если у объектов Point одинаковое расположение и
одинаковый тип; иначе False.</returns>
<seealso cref="M:Graphics.Point.Equals(System.Object)"/>
<seealso
cref="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"/>
</member>

<member
name="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)">
<summary>Этот оператор определяет, находятся ли два объекта Point в
одном
расположении.</summary>
<param><c>p1</c> — это первый сравниваемый объект Point.</param>
<param><c>p2</c> — это второй сравниваемый объект Point.</param>
<returns>True, если у объектов Point одинаковое расположение и
одинаковый тип; иначе False.</returns>
<seealso cref="M:Graphics.Point.Equals(System.Object)"/>
<seealso
cref="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)"/>
</member>

<member name="M:Graphics.Point.Main">
<summary>Это точка входа программы тестирования класса Point.
<para>Эта программа тестирует каждый метод и оператор, и
ее предполагается выполнять после каждого нетривиального
обслуживания класса Point.</para></summary>
</member>

<member name="P:Graphics.Point.X">
<value>Свойство <c>X</c> представляет
координату x точки.</value>
</member>

<member name="P:Graphics.Point.Y">
<value>Свойство <c>Y</c> представляет
координату y точки.</value>
</member>
</members>
</doc>


B. Грамматика

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

B.1 Лексика

ввод:
раздел_вводанеобязательно

раздел_ввода:
часть_раздела_ввода
раздел_ввода часть_раздела_ввода

часть_раздела_ввода:
элементы_вводанеобязательно новая_строка
директива_предварительной_обработки

элементы_ввода:
элемент_ввода
элементы_ввода элемент_ввода

элемент_ввода:
пробел
примечание
маркер

B.1.1 Знаки завершения строки

новая_строка:
Символ возврата каретки (U+000D)
Символ перевода строки (U+000A)
Символ возврата каретки (U+000D), за которым следует знак перевода строки (U+000A)
Символ следующей строки (U+0085)
Символ разделителя строк (U+2028)
Символ разделителя абзацев (U+2029)

B.1.2 Комментарии

комментарий:
однострочный_комментарий
комментарий_с_разделами

однострочный_комментарий:
// входные_символынеобязательно

входные_символы:
входной_символ
входные_символы входной_символ

входной_символ:
Любой знак Юникода за исключением символа_следующей_строки

символ_следующей_строки:
Символ возврата каретки (U+000D)
Символ перевода строки (U+000A)
Символ следующей строки (U+0085)
Символ разделителя строк (U+2028)
Символ разделителя абзацев (U+2029)

комментарий_с_разделами:
/* текст_комментария_с_разделаминеобязательно звездочка /

текст_комментария_с_разделами:
раздел_комментария_с_разделами
текст_комментария_с_разделами раздел_комментария_с_разделами

раздел_комментария_с_разделами:
/
звездочканеобязательно не_косая_черта_и_не_звездочка

звездочка:
*
звездочка *

не_косая_черта_и_не_звездочка:
Любой знак Юникода за исключением косой черты (/) или звездочки (*)

B.1.3 Пробел

пробел:
Любой символа класса Юникода Zs
Символ горизонтальной табуляции (U+0009)
Символ вертикальной табуляции (U+000B)
Символ перехода к другой форме (U+000C)

B.1.4 Маркеры

маркер:
идентификатор
ключевое_слово
целочисленный_литерал
действительный_литерал
символьный_литерал
строковый_литерал
оператор_или_знак_пунктуации

B.1.5 Управляющие последовательности символов Юникода

управляющая_последовательность_Юникода:
\u шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра
\U шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра шестнадцатеричная_цифра

B.1.6 Идентификаторы

идентификатор:
доступный_идентификатор
@ идентификатор_или_ключевое_слово

доступный_идентификатор:
Идентификатор_или_ключевое_слово, не являющиеся ключевым_словом

идентификатор_или_ключевое_слово:
начальный_символ_идентификатора символы_части_идентификаторанеобязательно

начальный_символ_идентификатора:
буква
_ (знак подчеркивания, U+005F)

символы_части_идентификатора:
символ_части_идентификатора
символы_части_идентификатора символ_части_идентификатора

символ_части_идентификатора:
буква
десятичная_цифра
соединитель
несамостоятельный_символ
символ_управления_форматом

буква:
Символ Юникода классов Lu, Ll, Lt, Lm, Lo или Nl
Управляющая_последовательность_Юникода, представляющая символы классов Lu, Ll, Lt, Lm, Lo или Nl

несамостоятельный_символ:
Символ Юникода классов Mn или Mc
Управляющая_последовательность_Юникода, представляющая символ классов Mn или Mc

десятичная_цифра:
Символ Юникода класса Nd
Управляющая_последовательность_Юникода, представляющая символ класса Nd

соединитель:
Символ Юникода класса Pc
Управляющая_последовательность_Юникода, представляющая символ класса Pc

символ_управления_форматом:
Символ Юникода класса Cf
Управляющая_последовательность_Юникода, представляющая символ класса Cf

B.1.7 Ключевые слова

ключевое_слово: одно из следующих:
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

B.1.8 Литералы

литерал:
логический_литерал
целочисленный_литерал
действительный_литерал
символьный_литерал
строковый_литерал
литерал_null

логический_литерал:
true
false

целочисленный_литерал:
десятичный_целочисленный_литерал
шестнадцатеричный_целочисленный_литерал

десятичный_целочисленный_литерал:
десятичные_цифры суффикс_целочисленного_типанеобязательно

десятичные_цифры:
десятичная_цифра
десятичные_цифры десятичная_цифра

десятичная_цифра: одна из следующих:
0 1 2 3 4 5 6 7 8 9

суффикс_целочисленного_типа: один из следующих:
U u L l UL Ul uL ul LU Lu lU lu

шестнадцатеричный_целочисленный_литерал:
0x шестнадцатеричные_цифры суффикс_целочисленного_типанеобязательно
0X шестнадцатеричные_цифры суффикс_целочисленного_типанеобязательно

шестнадцатеричные_цифры:
шестнадцатеричная_цифра
шестнадцатеричные_цифры шестандцатеричная_цифра

шестнадцатеричная_цифра: одна из следующих:
0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f

действительный_литерал:
десятичные_цифры . десятичные_цифры порядок_числанеобязательно суффикс_действительного_числанеобязательно
. десятичные_цифры порядок_числанеобязательно суффикс_действительного_числанеобязательно
десятичные_цифры порядок_числа суффикс_действительного_числанеобязательно
десятичные_цифры суффикс_действительного_числа

порядок_числа:
e знакнеобязательно десятичное_число
E знакнеобязательно десятичное_число

знак: один из следующих:
+ -

суффикс_действительного_числа: один из следующих:
F f D d M m

символьный_литерал:
' символ '

символ:
отдельный_символ
простая_управляющая_последовательность
шестнадцатеричная_управляющая_последовательность
управляющая_последовательность_Юникода

отдельный_знак:
Любой знак за исключением знака апострофа (') (U+0027), косой черты (\) (U+005C) и символа_следующей_строки

простая_управляющая_последовательность: одно из следующих:
\' \" \\ \0 \a \b \f \n \r \t \v

шестнадцатеричная_управляющая_последовательность:
\x шестнадцатеричная_цифра шестнадцатеричная_цифранеобязательно шестнадцатеричная_цифранеобязательно шестнадцатеричная_цифранеобязательно

строковый_литерал:
правильный_строковый_литерал
буквальный_строковый_литерал

правильный_строковый_литерал:
" символы_правильного_строкового_литераланеобязательно "

символы_правильного_строкового_литерала:
символ_правильного_строкового_литерала
символы_правильного_строкового_литерала символ_правильного_строкового_литерала

символ_правильного_строкового_литерала:
один_символ_правильного_строкового_литерала
простая_управляющая_последовательность
шестнадцатеричная_управляющая_последовательность
управляющая_последовательность_Юникода

один_символ_правильного_строкового_литерала:
Любой символ за исключением символа кавычек (") (U+0022), косой черты (\) (U+005C) и символа_следующей_строки

буквальный_строковый_литерал:
@" символы_буквального_строкового_литераланеобязательно "

символы_буквального_строкового_литерала:
символ_буквального_строкового_литерала
символы_буквального_строкового_литерала символ_буквального_строкового_литерала

символ_буквального_строкового_литерала:
один_символ_буквального_строкового_литерала
управляющая_последовательность_кавычки

один_символ_буквального_строкового_литерала:
любой символ за исключением "

управляющая_последовательность_кавычки:
""

литерал_null:
null

B.1.9 Операторы и знаки пунктуации

оператор_или_знак_пунктуации: один из следующих:
{ } [ ] ( ) . , : ;
+ - * / % & | ^ ! ~
= < > ? ?? :: ++ -- && ||
-> == != <= >= += -= *= /= %=
&= |= ^= << <<= =>

сдвиг_вправо:
>|>

присваивание_сдвига_вправо:
>|>=

B.1.10 Директивы предварительной обработки

ПРО_директива:
ПРО_описание
ПРО_условное_выражение
ПРО_строка
ПРО_диагностика
ПРО_область
ПРО_pragma

символ_условия:
Любые идентификатор_или_ключевое_слово за исключением true или false

ПРО_выражение:
пробелнеобязательно ПРО_выражение_ИЛИ пробелнеобязательно

ПРО_выражение_ИЛИ:
ПРО_выражение_И
ПРО_выражение_ИЛИ пробелнеобязательно || пробелнеобязательно ПРО_выражение_И

ПРО_выражение_И:
ПРО_выражение_равенства
ПРО_выражение_И пробелнеобязательно && пробелнеобязательно ПРО_выражение_равенства

ПРО_выражение_равенства:
ПРО_унарное_выражение
ПРО_выражение_равенства пробелнеобязательно == пробелнеобязательно ПРО_унарное_выражение
ПРО_выражение_равенства пробелнеобязательно != пробелнеобязательно ПРО_унарное_выражение

ПРО_унарное_выражение:
ПРО_первичное_выражение
! пробелнеобязательно ПРО_унарное_выражение

ПРО_первичное_выражение:
true
false
символ_условия
( пробелнеобязательно ПРО_выражение пробелнеобязательно )

ПРО_описание:
пробелнеобязательно # пробелнеобязательно define пробел символ_условия ПРО_новая_строка
пробелнеобязательно # пробелнеобязательно undef пробел символ_условия ПРО_новая_строка

ПРО_новая_строка:
пробелнеобязательно однострочный_комментарийнеобязательно следующая_строка

ПРО_условное_выражение:
ПРО_раздел_if ПРО_разделы_elifнеобязательно ПРО_раздел_elseнеобязательно ПРО_endif

ПРО_раздел_if:
пробелнеобязательно # пробелнеобязательно if пробел ПРО_выражение ПРО_новая_строка раздел_условного_выражениянеобязательно

ПРО_разделы_elif:
ПРО_раздел_elif
ПРО_разделы_elif ПРО_раздел_elif

ПРО_раздел_elif:
пробелнеобязательно # пробелнеобязательно elif пробел ПРО_выражение ПРО_новая_строка раздел_условного_выражениянеобязательно

ПРО_раздел_else:
пробелнеобязательно # пробелнеобязательно else ПРО_новая_строка раздел_условного_выражениянеобязательно

ПРО_endif:
пробелнеобязательно # пробелнеобязательно endif ПРО_новая_строка

раздел_условного_выражения:
раздел_ввода
пропущенный_раздел

пропущенные_символы:
часть_пропущенных_символов
пропущенный символ часть_пропущенных_символов

часть_пропущенных_символов:
пропущенные_символынеобязательно новая_строка
ПРО_директива

пропущенные_символы:
пробелнеобязательно не_знак_числа входные_символынеобязательно

не_знак_числа:
Любой входной_знак за исключением знака решетки (#)

ПРО_диагностика:
пробелнеобязательно # пробелнеобязательно error ПРО_сообщение
пробелнеобязательно # пробелнеобязательно warning ПРО_сообщение

ПРО_сообщение:
новая_строка
пробел входные_символынеобязательно новая_строка

ПРО_область:
ПРО_начальная_область раздел_условного_выражениянеобязательно ПРО_конечная_область

ПРО_начальная_область:
пробелнеобязательно # пробелнеобязательно region ПРО_сообщение

ПРО_конечная_область:
пробелнеобязательно # пробелнеобязательно endregion ПРО_сообщение

ПРО_строка:
пробелнеобязательно # пробелнеобязательно line пробел индикатор_строки ПРО_новая_строка

индикатор_строки:
десятичные_цифры пробел имя_файла
десятичные_цифры
default
hidden

имя_файла:
" символы_имени_файла "

символы_имени_файла:
символ_имени_файла
символы_имени_файла символ_имени_файла

символ_имени_файла:
Любой входной_знак за исключением "

ПРО_pragma:
пробелнеобязательно # пробелнеобязательно pragma пробел тело_директивы_pragma ПРО_новая_строка

тело_директивы_pragma:
тело_предупреждения_директивы_pragma

тело_предупреждения_директивы_pragma:
warning пробел действие_предупреждения
warning пробел действие_предупреждения пробел список_предупреждений

действие_предупреждения:
disable
restore

список_предупреждений:
десятичные_цифры
список_предупреждений пробелнеобязательно , пробелнеобязательно десятичные_цифры

B.2 Синтаксис

B.2.1 Базовые концепции

имя_пространства_имен:
имя_пространства_имен_или_типа

имя_типа:
имя_пространства_имен_или_типа

имя_пространства_имен_или_типа:
идентификатор список_аргументов_типанеобязательно
имя_пространства_имен_или_типа . идентификатор список_аргументов_типанеобязательно
уточненный_член_псевдонима

B.2.2 Типы

тип:
тип_значения
ссылочный_тип
параметр_типа

тип_значения:
тип_struct
перечисляемый_тип

тип_struct:
имя_типа
простой_тип
обнуляемый_тип

простой_тип:
числовой_тип
bool

числовой_тип:
целый_тип
тип_с_плавающей_запятой
decimal

целый_тип:
sbyte
byte
short
ushort
int
uint
long
ulong
char

тип_с_плавающей_запятой:
float
double

обнуляемый_тип:
non-nullable-value-type ?

не_обнуляемый_тип_значений:
тип

перечисляемый_тип:
имя_типа

ссылочный_тип:
тип_класса
тип_интерфейса
тип_массива
тип_делегата

тип_класса:
имя_типа
object
string

тип_интерфейса:
имя_типа

тип_массива:
тип_не_массива спецификации_ранга

тип_не_массива:
тип

спецификации_ранга:
спецификация_ранга
спецификации_ранга спецификация_ранга

спецификация_ранга:
[ разделители_размерностейнеобязательно ]

разделители_размерностей:
,
разделители_размерностей ,

тип_делегата:
имя_типа

список_аргументов_типа:
< аргументы_типа >

аргументы_типа:
аргумент_типа
аргументы_типа , аргумент_типа

аргумент_типа:
тип

параметр_типа:
идентификатор

B.2.3 Переменные

ссылочная_переменная:
выражение

B.2.4 Выражения

список_аргументов:
аргумент
список_аргументов , аргумент

аргумент:
выражение
ref ссылочная_переменная
out ссылочная_переменная

первичное_выражение:
primary-no-array-creation-expression
array-creation-expression

первичное_выражение_создания_не_массива:
литерал
простое_имя
выражение_в_скобках
метод_доступа_к_члену
выражение_вызова
метод_доступа_к_элементу
доступ_через_this
доступ_к_базовому_объекту
выражение_после_инкремента
выражение_после_декремента
выражение_создания_объекта
выражение_создания_делегата
выражение_создания_анонимного_объекта
выражение_typeof
выражение_checked
выражение_unchecked
выражение_значения_по_умолчанию
выражение_анонимного_метода

простое_имя:
индетификатор список_аргументов_типанеобязательно

выражение_в_скобках:
( выражение )

метод_доступа_к_члену:
первичное_выражение . идентификатор список_аргументов_типанеобязательно
встроенный_тип идентификатор список_аргументов_типанеобязательно
уточненный_член_псевдонима идентификатор

встроенный тип: один из следующих:
bool byte char decimal double float int long
object sbyte short string uint ulong ushort

выражение_вызова:
первичное_выражение ( список_аргументовнеобязательно )

метод_доступа_к_элементу:
первичное_выражение_создания_не_массива [ список_выражений ]

список_выражений:
выражение
список_выражений , выражение

доступ_через_this:
this

доступ_к_базовому_объекту:
base . идентификатор
base [ список_выражений ]

выражение_после_инкремента:
первичное_выражение ++

выражение_после_декремента:
первичное_выражение --

выражение_создания_объекта:
new тип ( список_аргументовнеобязательно ) инициализатор_объекта_или_коллекциинеобязательно
new тип инициализатор_объекта_или_коллекции

инициализатор_объекта_или_коллекции:
инициализатор_объекта
инициализатор_коллекции

инициализатор_объекта:
{ список_инициализаторов_членовнеобязательно }
{ список_инициализаторов_членов , }

список_инициализаторов_членов:
инициализатор_члена
список_инициализаторов_членов , инициализатор_члена

инициализатор_члена:
идентификатор = значение_идентификатора

значение_идентификатора:
выражение
инициализатор_объекта_или_коллекции

инициализатор_коллекции:
{ список_инициализаторов_элементов }
{ список_инициализаторов_элементов , }

список_инициализаторов_элементов:
инициализатор_элемента
список_инициализаторов_элементов , инициализатор_элемента

инициализатор_элемента:
выражение_не_присваивания
{ список_выражений }

выражение_создания_массива:
new тип_не_массива [ список_выражений ] спецификации_ранганеобязательно инициализатор_массиванеобязательно
new тип_массива инициализатор_массива
new спецификация_ранга инициализатор_массива

выражение_создания_делегата:
new тип_делегата ( выражение )

выражение_создания_анонимного_объекта:
new инициализатор_анонимного_объекта

инициализатор_анонимного_объекта:
{ список_деклараторов_членовнеобязательно }
{ список_деклараторов_членов , }

список_деклараторов_членов:
декларатор_члена
список_деклараторов_членов , декларатор_члена

декларатор_члена:
простое_имя
метод_доступа_к_члену
идентификатор = выражение

выражение_typeof:
typeof ( тип )
typeof ( имя_непривязанного_типа )
typeof ( void )

имя_непривязанного_типа:
идентификатор спецификация_универсального_измерениянеобязательно
идентификатор :: идентификатор спецификация_универсального_измерениянеобязательно
имя_непривязанного_типа . идентификатор спецификация_универсального_измерениянеобязательно

спецификация_универсального_измерения:
< запятыенеобязательно >

запятые:
,
запятые ,

выражение_checked:
checked ( выражение )

выражение_unchecked:
unchecked ( выражение )

выражение_значения_по_умолчанию:
default ( тип )

унарное_выражение:
первичное_выражение
+ унарное_выражение
- унарное_выражение
! унарное_выражение
~ унарное_выражение
выражение_до_инкремента
выражение_до_декремента
выражение_приведения_к_типу

выражение_до_инкремента:
++ унарное_выражение

выражение_до_декремента:
-- унарное_выражение

выражение_приведения_к_типу:
( тип ) унарное_выражение

мультипликативное_выражение:
унарное_выражение
мультипликативное_выражение * унарное_выражение
мультипликативное_выражение / унарное_выражение
мультипликативное_выражение % унарное_выражение

аддитивное_выражение:
мультипликативное_выражение
аддитивное_выражение + мультипликативное_выражение
аддитивное_выражение – мультипликативное_выражение

выражение_сдвига:
аддитивное_выражение
выражение_сдвига << аддитивное_выражение
выражение_сдвига сдвиг_вправо аддитивное_выражение

реляционное_выражение:
выражение_сдвига
реляционное_выражение < выражение_сдвига
реляционное_выражение > выражение_сдвига
реляционное_выражение <= выражение_сдвига
реляционное_выражение >= выражение_сдвига
реляционное_выражение is тип
реляционное_выражение as тип

выражение_равенства:
реляционное_выражение
выражение_равенства == реляционное_выражение
выражение_равенства != реляционное_выражение

выражение_И:
выражение_равенства
выражение_И & выражение_равенства

выражение_исключающего_ИЛИ:
выражение_И
выражение_исключающего_ИЛИ ^ выражение_И

выражение_включающего_ИЛИ:
выражение_исключающего_ИЛИ
выражение_включающего_ИЛИ | выражение_исключающего_ИЛИ

выражение_условного_И:
выражение_включающего_ИЛИ
выражение_условного_И && выражение_включающего_ИЛИ

выражение_условного_ИЛИ:
выражение_условного_И
выражение_условного_ИЛИ || выражение_условного_И

выражение_слияния_с_null:
выражение_условного_ИЛИ
выражение_условного_ИЛИ ?? выражение_слияния_с_null

условное_выражение:
выражение_слияния_с_null
выражение_слияния_с_null ? выражение : выражение

лямбда_выражение:
подпись_анонимной_функции => тело_анонимной_функции

выражение_анонимного_метода:
delegate подпись_явной_анонимной_функциинеобязательно блок

подпись_анонимной_функции:
подпись_явной_анонимной_функции
подпись_неявной_анонимной_функции

подпись_явной_анонимной_функции:
( список_параметров_явной_анонимной_функциинеобязательно )

список_параметров_явной_анонимной_функции
параметр_явной_анонимной_функции
список_параметров_явной_анонимной_функции , параметр_явной_анонимной_функции

параметр_явной_анонимной_функции:
модификатор_параметра_анонимной_функциинеобязательно тип идентификатор

модификатор_параметра_анонимной_функции:
ref
out

подпись_неявной_анонимной_функции:
( список_параметров_неявной_анонимной_функциинеобязательно )
параметр_неявной_анонимной_функции

список_параметров_неявной_анонимной_функции
параметр_неявной_анонимной_функции
список_параметров_неявной_анонимной_функции , параметр_неявной_анонимной_функции

параметр_неявной_анонимной_функции:
идентификатор

тело_анонимной_функции:
выражение
блок

выражение_запроса:
предложение_from тело_запроса

предложение_from:
from типнеобязательно идентификатор in выражение

тело_запроса:
предложения_тела_запросанеобязательно предложение_select_или_group дополнительный_запроснеобязательно

предложения_тела_запроса:
предложение_тела_запроса
предложения_тела_запроса предложение_тела_запроса

предложение_тела_запроса:
предложение_from
предложение_let
предложение_where
предложение_join
предложение_join-into
предложение_orderby

предложение_let:
let идентификатор = выражение

предложение_where:
where логическое_выражение

предложение_join:
join типнеобязательно идентификатор in выражение on выражение equals выражение

предложение_join-into:
join типнеобязательно идентификатор in выражение on выражение equals выражение into идентификатор

предложение_orderby:
orderby упорядочения

упорядочения:
упорядочение
упорядочения , упорядочение

упорядочение:
выражение направление_упорядочениянеобязательно

направление_упорядочения:
ascending
descending

предложение_select_или_group:
предложение_select
предложение_group

предложение_select:
select выражение

предложение_group:
group выражение by выражение

объединение_запросов:
into идентификатор тело_запроса

присваивание:
унарное_выражение оператор_присваивания выражение

оператор_присваивания:
=
+=
-=
*=
/=
%=
&=
|=
^=
<<=
присваивание_со_сдвигом_вправо

выражение:
выражение_не_присваивания
присваивание

выражение_не_присваивания:
условное_выражение
выражение_лямбда
выражение_запроса

константное_выражение:
выражение

логическое_выражение:
выражение

B.2.5 Операторы

оператор:
помеченный_оператор
оператор_объявления
встроенный_оператор

встроенный_оператор:
блок
пустой_оператор
оператор_выражения
оператор_выбора
оператор_итерации
оператор_перехода
оператор_try
оператор_checked
опертор_unchecked
оператор_lock
оператор_using
оператор_yield

блок:
{ список_операторовнеобязательно }

список_операторов:
оператор
список_операторов оператор

пустой_оператор:
;

помеченный_оператор:
идентификатор : оператор

оператор_объявления:
объявление_локальной_переменной ;
объявление_локальной_константы ;

объявление_локальнной_переменной:
тип_локальной_переменной деклараторы_локальных_переменных

тип_локальной_переменной:
тип
var

деклараторы_локальных_переменных:
декларатор_локальной_переменной
деклараторы_локальных_переменных , декларатор_локальной_переменной

декларатор_локальной_переменной:
идентификатор
идентификатор = инициализатор_локальной_переменной

инициализатор_локальной_переменной:
выражение
инициализатор_массива

объявление_локальной_константы:
const тип объявления_констант

объявления_констант:
объявление_константы
объявления_констант , объявление_константы

объявление_константы:
идентификатор = выражение_константы

оператор_выражения:
выражение_оператора ;

выражение_оператора:
выражение_вызова
выражение_создания_объекта
присваивание
выражение_после_инкремента
выражение_после_декремента
выражение_до_инкремента
выражение_до_декремента

оператор_выбора:
оператор_if
оператор_switch

оператор_if:
if ( логическое_выражение ) внедренный_оператор
if ( логическое_выражение ) внедренный_оператор else внедренный_оператор

оператор_switch:
switch ( выражение ) блок_switch

блок_switch:
{ разделы_switchнеобязательно }

разделы_switch:
раздел_switch
разделы_switch раздел_switch

раздел_switch
метки_switch список_операторов

метки_switch:
метка_switch
метки_switch метка_switch

метка_switch:
case константное_выражение :
default :

оператор_итераций:
оператор_while
оператор_do
оператор_for
оператор_foreach

оператор_while:
while ( логическое_выражение ) внедренный_оператор

оператор_do:
do внедренный_оператор while ( логическое_выражение ) ;

оператор_for:
for ( инициализатор_forнеобязательно ; условие_forнеобязательно ; итератор_forнеобязательно ) внедренный_оператор

инициализатор_for:
объявление_локальной_переменной
список_выражений_оператора

условие_for:
логическое выражение

итератор_for:
список_выражений_операторов

список_выражений_операторов:
выражение_оператора
список_выражений_операторов , выражение_оператора

оператор_foreach:
foreach ( тип_локальной_переменной идентификатор in выражение ) встроенный_оператор

оператор_jump:
оператор_break
оператор_continue
оператор_goto
оператор_return
оператор_throw

оператор_break:
break ;

оператор_continue:
continue ;

оператор_goto:
goto идентификатор ;
goto case константное_выражение ;
goto default ;

оператор_return:
return выражениенеобязательно ;

оператор_throw:
throw выражениенеобязательно ;

оператор_try:
try блок предложения_catch
try блок предложение_finally
try блок предложения_catch предложение_ finally

предложения_catch:
конкретные_предложения_catch общее_предложение_catchнеобязательно
конкретные_предложения_catchнеобязательно общее_предложение_catch

конкретные_предложения_catch:
конкретное_предложение_catch
конкретные_предложения_catch конкретное_предложение_catch

конкретное_предложение_catch:
catch ( тип_класса идентификаторнеобязательно ) блок

общее_предложение_catch:
catch блок

предложение_finally:
finally блок

оператор_checked:
checked блок

оператор_unchecked:
unchecked блок

оператор_lock:
lock ( выражение ) внедренный_оператор

оператор_using:
using ( получение_ресурса ) внедренный_оператор

получение_ресурса:
объявление_локальной_переменной
выражение

оператор_yield:
yield return выражение ;
yield break ;

B.2.6 Пространства имен

модуль_компиляции:
директивы_внешнего_псевдониманеобязательно директивы_usingнеобязательно глобальные_атрибутынеобязательно
объявления_элементов_пространства_именнеобязательно

объявление_пространства_имен:
namespace проверенный_идентификатор тело_пространства_имен ;необязательно

проверенный_идентификатор:
идентификатор
проверенный_идентификатор . идентификатор

тело_пространства_имен:
{ директивы_внешних_псевдонимовнеобязательно директивы_usingнеобязательно объявления_элементов_пространства_именнеобязательно }

директивы_внешних_псевдонимов:
директива_внешнего_псевдонима
директивы_внешних_псевдонимов директива_внешнего_псевдонима

директива_внешнего_псевдонима:
extern alias идентификатор ;

директивы_using:
директива_using
директивы_using директива_using

директива_using:
директива_using_для_псевдонима
директива_using_для_пространства_имен

директива_using_для_пространства_имен:
using идентификатор = имя_пространства_имен_или_типа ;

директива_using_для_пространства_имен:
using имя_пространства_имен ;

объявления_элементов_пространства_имен:
объявление_элементов_пространства_имен
объявления_элементов_пространства_имен объявление_элемента_пространства_имен

объявление_элемента_пространства_имен:
обяъвление_пространства_имен
объявление_типа

объявление_типа:
объявление_класса
объявление_структуры
объявление_интерфейса
объявление_перечисления
обяъвление_делегата

проверенный_псевдоним_элемента:
идентификатор :: идентификатор список_аргументов_типанеобязательно

B.2.7 Классы

объявление_класса:
атрибутынеобязательно модификаторы_классанеобязательно partialнеобязательно class идентификатор список_параметров_типанеобязательно
база_классанеобязательно предложения_ограничений_параметров_типанеобязательно тело_класса ;необязательно

модификаторы_класса:
модификатор_класса
модификатор_класса модификаторы_класса

модификатор_класса:
new
public
protected
internal
private
abstract
sealed
static

список_параметров_типа:
< параметры_типа >

параметры_типа:
атрибутынеобязательно параметр_типа
параметры_типа , атрибутынеобязательно параметр_типа

параметр_типа:
идентификатор

база_класса:
: тип_класса
: список_типов_интерфейсов
: тип_класса , список_типов_интерфейсов

список_типов_интерфейсов:
тип_интерфейса
список_типов_интерфейсов , тип_интерфейса

предложения_ограничений_параметров_типа:
предложение_ограничений_параметров_типа
предложения_ограничений_параметров_типа предложение_ограничений_параметров_типа

предложение_ограничений_параметров_типа:
where параметр_типа : ограничения_параметров_типа

ограничения_параметров_типа:
первичное_ограничение
вторичные_ограничения
ограничение_для_конструктора
первичное_ограничение , вторичные_ограничения
первичное_ограничение , ограничение_для_конструктора
вторичные_ограничения , ограничение_для_конструктора
первичное_ограничение , вторичные_ограничения , ограничение_для_конструктора

первичное_ограничение:
тип_класса
class
struct

вторичные_ограничения:
тип_интерфейса
параметр_типа
вторичные_ограничения , тип_интерфейса
вторичные_ограничения , параметр_типа

ограничение_для_конструктора:
new ( )

тело_класса:
{ объявления_элементов_классанеобязательно }

объявления_элементов_класса:
объявление_элемента_класса
объявления_элементов_класса объявление_элемента_класса

объявление_элемента_класса:
объявление_константы
объявление_поля
объявление_метода
объявление_свойства
объявление_события
объявление_индекса
объявление_оператора
объявление_конструктора
объявление_деструктора
объявление_статического_конструктора
объявление_типа

объявление_константы:
атрибутынеобязательно модификаторы_константынеобязательно const тип деклараторы_константы ;

модификаторы_константы:
модификатор_константы
модификаторы_константы модификатор_константы

модификатор_константы:
new
public
protected
internal
private

деклараторы_констант:
декларатор_константы
деклараторы_констант , декларатор_константы

декларатор_константы:
идентификатор = выражение_константы

объявление_поля:
атрибутынеобязательно модификаторы_полянеобязательно тип деклараторы_переменных ;

модификаторы_поля:
модификатор_поля
модификаторы_поля модификатор_поля

модификатор_поля:
new
public
protected
internal
private
static
readonly
volatile

деклараторы_переменных:
декларатор_переменной
деклараторы_переменных , декларатор_переменной

декларатор_переменной:
идентификатор
идентификатор = инициализатор_переменной

инициализатор_переменной:
выражение
инициализатор_массива

объявление_метода:
заголовок_метода тело_метода

заголовок_метода:
атрибутынеобязательно модификаторы_методанеобязательно partialнеобязательно тип_возвращаемого_значения имя_элемента список_параметров_типанеобязательно
( список_формальных_параметровнеобязательно ) предложения_ограничений_параметров_типанеобязательно

модификаторы_метода:
модификатор_метода
модификаторы_метода модификатор_метода

модификатор_метода:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

тип_возвращаемого_значения:
тип
void

имя_элемента:
идентификатор
тип_интерфейса . идентификатор

тело_метода:
блок
;

список_формальных_параметров:
фиксированные_параметры
фиксированные_параметры , массив_параметров
массив_параметров

фиксированные_параметры:
фиксированный_параметр
фиксированные_параметры , фиксированный_параметр

фиксированный_параметр:
атрибутынеобязательно модификатор_параметранеобязательно тип идентификатор

модификатор_параметра:
ref
out
this

массив_параметров:
атрибутынеобязательно params тип_массива идентификатор

объявление_свойства:
атрибутынеобязательно модификаторы_свойстванеобязательно тип имя_элемента { объявления_методов_доступа }

модификаторы_свойства:
модификатор_свойства
модификаторы_свойства модификатор_свойства

модификатор_свойства:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

имя_элемента:
идентификатор
тип_интерфейса . идентификатор

объявления_методов_доступа:
объявление_метода_доступа_get объявление_метода_доступа_setнеобязательно
объявление_метода_доступа_set объявление_метода_доступа_getнеобязательно

объявление_метода_доступа_get:
атрибутынеобязательно модификатор_метода_доступанеобязательно get тело_метода_доступа

объявление_метода_доступа_set:
атрибутынеобязательно модификатор_метода_доступанеобязательно set тело_метода_доступа

модификатор_метода_доступа:
protected
internal
private
protected internal
internal protected

тело_метода_доступа:
блок
;

объявление_события:
атрибутынеобязательно модификаторы_событиянеобязательно event тип деклараторы_переменных ;
атрибутынеобязательно модификаторы_событиянеобязательно event тип имя_элемента { объявления_методов_доступа_для_событий }

модификаторы_события:
модификатор_события
модификаторы_события модификатор_события

модификатор_события:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

объявления_методов_доступа_для_событий:
объявление_метода_доступа_add объявление_метода_доступа_remove
объявление_метода_доступа_remove объявление_метода_доступа_add

объявление_метода_доступа_add:
атрибутынеобязательно add блок

объявление_метода_доступа_remove:
атрибутынеобязательно remove блок

объявление_индекса:
атрибутынеобязательно модификаторы_индексанеобязательно декларатор_индекса { объявления_методов_доступа }

модификаторы_индекса:
модификатор_индекса
модификаторы_индекса модификатор_индекса

модификатор_индекса:
new
public
protected
internal
private
virtual
sealed
override
abstract
extern

декларатор_индекса:
тип this [ список_формальных_параметров ]
тип тип_интерфейса . this [ список_формальных_параметров ]

объявление_оператора:
атрибутынеобязательно модификаторы_оператора декларатор_оператора тело_оператора

модификаторы_оператора:
модификатор_оператора
модификаторы_оператора модификатор_оператора

модификатор_оператора:
public
static
extern

декларатор_оператора:
декларатор_унарного_оператора
декларатор_двоичного_оператора
декларатор_оператора_преобразования

декларатор_унарного_оператора:
тип operator перегружаемый_унарный_оператор ( тип идентификатор )

перегружаемый_унарный_оператор: один из следующих
+ - ! ~ ++ -- true false

декларатор_двоичного_оператора:
тип operator перегружаемый_двоичный_оператор ( тип идентификатор , тип идентификатор )

перегружаемый_двоичный_оператор:
+
-
*
/
%
&
|
^
<<
сдвиг_вправо
==
!=
>
<
>=
<=

декларатор_оператора_преобразования:
implicit operator тип ( тип идентификатор )
explicit operator тип ( тип идентификатор )

тело_оператора:
блок
;

объявление_конструктора:
атрибутынеобязательно модификаторы_конструктора декларатор_конструктора тело_конструктора

модификаторы_конструктора:
модификатор_конструктора
модификаторы_конструктора модификатор_конструктора

модификатор_конструктора:
public
protected
internal
private
extern

декларатор_конструктора:
идентификатор ( список_формальных_параметровнеобязательно ) инициализатор_конструкторанеобязательно

инициализатор_конструктора:
: base ( список_аргументовнеобязательно )
: this ( список_аргументовнеобязательно )

тело_конструктора:
блок
;

объявление_статического_конструктора:
атрибутынеобязательно модификаторы_статического_конструктора идентификатор ( ) тело_статического_конструктора

модификаторы_статического_конструктора:
externнеобязательно static
static externнеобязательно

тело_статического_конструктора:
блок
;

объявление_деструктора:
атрибутынеобязательно externнеобязательно ~ идентификатор ( ) тело_деструктора

тело_деструктора:
блок
;

B.2.8 Структуры

объявление_структуры:
атрибутынеобязательно модификаторы_структурынеобязательно partialнеобязательно struct идентификатор список_параметров_типанеобязательно
интерфейсы_структурынеобязательно предложения_ограничений_параметров_типанеобязательно тело_структуры ;необязательно

модификаторы_структуры:
модификатор_структуры
модификатор_структуры модификаторы_структуры

модификатор_структуры:
new
public
protected
internal
private

интерфейсы_структуры:
: список_типов_интерфейса

тело_структуры:
{ объявления_элементов_структурынеобязательно }

объявления_элементов_структуры:
объявление_элементов_структуры
объявления_элементов_структуры объявление_элемента_структуры

объявление_элемента_структуры:
объявление_константы
объявление_поля
объявление_метода
объявление_свойства
объявление_события
объявление_индекса
объявление_оператора
объявление_конструктора
объявление_статического_конструктора
объявление_типа

B.2.9 Массивы

тип_массива:
тип_не_массива спецификации_ранга

тип_не_массива:
тип

спецификации_ранга:
спецификация_ранга
спецификации_ранга спецификация_ранга

спецификация_ранга:
[ разделители_размерностейнеобязательно ]

разделители_размерностей:
,
разделители_размерностей ,

инициализатор_массива:
{ список_инициализаторов_переменныхнеобязательно }
{ список_инициализаторов_переменных , }

список_инициализаторов_переменных:
инициализатор_переменной
список_инициализаторов_переменных , инициализатор_переменной

инициализатор_переменной:
выражение
инициализатор_массива

B.2.10 Интерфейсы

объявление_интерфейса:
атрибутынеобязательно модификаторы_интерфейсанеобязательно partialнеобязательно interface идентификатор список_параметров_типанеобязательно
база_интерфейсанеобязательно предложения_ограничений_параметров_типанеобязательно тело_интерфейса ;необязательно

модификаторы_интерфейса:
модификатор_интерфейса
модификатор_интерфейса модификаторы_интерфейса

модификатор_интерфейса:
new
public
protected
internal
private

база_интерфейса:
: список_типов_интерфейса

тело_интерфейса:
{ объявления_элементов_интерфейсанеобязательно }

объявления_элементов_интерфейса:
объявление_элементов_интерфейса
объявления_элементов_интерфейса объявление_элемента_интерфейса

объявление_элемента_интерфейса:
объявление_метода_интерфейса
объявление_свойства_интерфейса
объявление_события_интерфейса
объявление_индекса_интерфейса

объявление_метода_интерфейса:
атрибутынеобязательно newнеобязательно тип_возвращаемого_значения идентификатор список_параметров_типа
( список_формальных_параметровнеобязательно ) предложения_ограничений_параметров_типанеобязательно ;

объявление_свойства_интерфейса:
атрибутынеобязательно newнеобязательно тип идентификатор { методы_доступа_к_интерфейсу }

методы_доступа_к_интерфейсу:
атрибутынеобязательно get ;
атрибутынеобязательно set ;
атрибутынеобязательно get ; атрибутынеобязательно set ;
атрибутынеобязательно set ; атрибутынеобязательно get ;

объявление_события_интерфейса:
атрибутынеобязательно newнеобязательно event тип идентификатор ;

объявление_индекса_интерфейса:
атрибутынеобязательно newнеобязательно тип this [ список_формальных_параметров ] { методы_доступа_к_интерфейсу }

B.2.11 Перечисления

объявление_перечисления:
атрибутынеобязательно модификаторы_перечислениянеобязательно enum идентификатор база_перечислениянеобязательно тело_перечисления ;необязательно

база_перечисления:
: целый_тип

тело_перечисления:
{ объявления_элементов_перечислениянеобязательно }
{ объявления_элементов_перечисления , }

модификаторы_перечисления:
модификатор_перечисления
модификатор_перечисления модификаторы_перечисления

модификатор_перечисления:
new
public
protected
internal
private

объявления_элементов_перечисления:
объявление_элементов_перечисления
объявления_элементов_перечисления объявление_элемента_перечисления

объявление_элемента_перечисления:
атрибутынеобязательно идентификатор
атрибутынеобязательно идентификатор = константное_выражение

B.2.12 Делегаты

объявление_делегата:
атрибутынеобязательно модификаторы_делегатанеобязательно delegate тип_возвращаемого_значения идентификатор список_параметров_типанеобязательно
( список_формальных_параметровнеобязательно ) предложения_ограничений_параметров_типанеобязательно ;

модификаторы_делегата:
модификатор_делегата
модификатор_делегата модификаторы_делегата

модификатор_делегата:
new
public
protected
internal
private

B.2.13 Атрибуты

глобальные_атрибуты:
разделы_глобальных_атрибутов

разделы_глобальных_атрибутов:
раздел_глобальных_атрибутов
разделы_глобальных_атрибутов раздел_глобальных_атрибутов

раздел_глобальных_атрибутов:
[ целевая_спецификация_глобального_атрибута список_атрибутов ]
[ целевая_спецификация_глобального_атрибута список_атрибутов , ]

целевая_спецификация_глобального_атрибута:
цель_глобального_атрибута :

цель_глобального_атрибута:
assembly
module

атрибуты:
разделы_атрибутов

разделы_атрибутов:
раздел_атрибутов
разделы_атрибутов раздел_атрибутов

раздел_атрибутов:
[ целевая_спецификация_атрибутанеобязательно список_атрибутов ]
[ целевая_спецификация_атрибутанеобязательно список_атрибутов , ]

целевая_спецификация_атрибута:
цель_атрибута :

цель_атрибута:
field
event
method
param
property
return
type

список_атрибутов:
атрибут
список_атрибутов , атрибут

атрибут:
имя_атрибута аргументы_атрибутанеобязательно

имя_атрибута:
имя_типа

аргументы_атрибута:
( список_аргументов_по_положениюнеобязательно )
( список_аргументов_по_положению , список_именованных_аргументов )
( список_именованных_аргументов )

список_аргументов_по_положению:
аргумент_по_положению
список_аргументов_по_положению , аргумент_по_положению

аргумент_по_положению:
выражение_аргумента_атрибута

список_именованных_аргументов:
именованный_аргумент
список_именованных_аргументов , именованный_аргумент

именованный_аргумент:
идентификатор = выражение_аргумента_атрибута

выражение_аргумента_атрибута:
выражение

B.3 Грамматические расширения для небезопасного кода

модификатор_класса:
...
unsafe

модификатор_структуры:
...
unsafe

модификатор_интерфейса:
...
unsafe

модификатор_делегата:
...
unsafe

модификатор_поля:
...
unsafe

модификатор_метода:
...
unsafe

модификатор_свойства:
...
unsafe

модификатор_события:
...
unsafe

модификатор_индекса:
...
unsafe

модификатор_оператора:
...
unsafe

модификатор_конструктора:
...
unsafe

объявление_деструктора:
атрибутынеобязательно externнеобязательно unsafeнеобязательно ~ идентификатор ( ) тело_деструктора
атрибутынеобязательно unsafeнеобязательно externнеобязательно ~ идентификатор ( ) тело_деструктора

модификаторы_статического_конструктора:
externнеобязательно unsafeнеобязательно static
unsafeнеобязательно externнеобязательно static
externнеобязательно static unsafeнеобязательно
unsafeнеобязательно static externнеобязательно
static externнеобязательно unsafeнеобязательно
static unsafeнеобязательно externнеобязательно

внедренный_оператор:
...
оператор_unsafe

оператор_unsafe:
unsafe блок

тип:
...
тип_указателя

тип_указателя:
неуправляемый_тип *
void *

неуправляемый_тип:
тип

первичное_выражение_создания_не_массива:
...
доступ_к_элементу_по_указателю
доступ_к_элементу_по_указателю
выражение_sizeof

унарное_выражение:
...
выражение_косвенного_обращения_к_указателю

выражение_косвенного_обращения_к_указателю:
* унарное_выражение

доступ_к_элементу_по_указателю:
первичное_выражение -> идентификатор

доступ_к_элементу_по_указателю:
первичное_выражение_создания_не_массива [ выражение ]

выражение_addressof:
& унарное_выражение

выражение_sizeof:
sizeof ( неуправляемый_тип )

внедренный_оператор:
...
фиксированный_оператор

фиксированный_оператор:
fixed ( тип_указателя деклараторы_фиксированных_указателей ) внедренный_указатель

деклараторы_фиксированных_указателей:
декларатор_фиксированного_указателя
деклараторы_фиксированных_указателей , декларатор_фиксированного_указателя

декларатор_фиксированного_указателя:
идентификатор = инициализатор_фиксированного_указателя

инициализатор_фиксированного_указателя:
& ссылка_на_переменную
выражение

объявление_элемента_структуры:

объявление_буфера_фиксированного_размера

объявление_буфера_фиксированного_размера:
атрибутынеобязательно модификаторы_буфера_фиксированного_размеранеобязательно fixed тип_элемента_буфера
деклараторы_буфера_фиксированного_размера ;

модификаторы_буфера_фиксированного_размера:
модификатор_буфера_фиксированного_размера
модификатор_буфера_фиксированного_размера модификаторы_буфера_фиксированного_размера

модификатор_буфера_фиксированного_размера:
new
public
protected
internal
private
unsafe

тип_элемента_буфера:
тип

деклараторы_буфера_фиксированного_размера:
декларатор_буфера_фиксированного_размера
декларатор_буфера_фиксированного_размера деклараторы_буфера_фиксированного_размера

декларатор_буфера_фиксированного_размера:
идентификатор [ константное_выражение ]

инициализатор_локальной_переменной:

инициализатор_stackalloc

инициализатор_stackalloc:
stackalloc неуправляемый_тип [ выражение ]


C. Ссылки

Консорциум по Юникоду. The Unicode Standard, Version 3.0. Addison-Wesley, Reading, Massachusetts, 2000, ISBN 0-201-616335-5.

IEEE. Стандарт IEEE для бинарной арифметики значений с плавающей запятой. Стандарт ANSI/IEEE 754-1985. Доступен на веб-узле http://www.ieee.org.

ISO/IEC. C++. ANSI/ISO/IEC 14882:1998.

– Конец работы –

Используемые теги: Спецификация, языка0.04

Если Вам нужно дополнительный материал на эту тему, или Вы не нашли то, что искали, рекомендуем воспользоваться поиском по нашей базе работ: C# Спецификация языка

Что будем делать с полученным материалом:

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

Еще рефераты, курсовые, дипломные работы на эту тему:

Два объекта истории русского языка: живой язык диалектный и литературный язык
Новые общественные функции приобретает русский язык по мере сложения новой исторической общности советского народа он становится межнациональным... Современный период... Горшкова Хабургаев ИГРЯ...

Понятие литературный язык. Место литературного языка среди других форм существования языка
Литературный язык это язык государственных и культурных учреждений школьного обучения радио и телевидения науки публицистики художественной... Современный литературный язык многофункционален Он используется в различных... Основные сферы использования литературного языка телевидение и кино наука и образование печать и радио...

Языковой материал для немецкого языка
Аспирация согласных p , t , k .Ассимиляция по глухости.Ударение и мелодика в немецком предложении. Ритмнемецкой речи. Ударение словесное, фразовое,… Отличие фонетического строя немецкого языка отфонетического строя русского… Склонение им н существительных.АртикльАртикль - служебное слово. Понятие о происхожденииартикля. Определ нный и…

Есть только одна раса — раса человечества, Есть только один язык — язык сердца, Есть только одна религия — религия любви
Мое отношение к религии или Что я вижу в мире Бога... Есть только одна раса раса человечества Есть только один язык язык... Есть только один Бог и Он вездесущ...

Изобретение языка: концепции возникновения языка от Демокрита до А.Смита
Первоначальные формы его выражения Язык наиболее объемлющее и наиболее дифференцированное средство выражения, которым владеет человек, и… Крик при этом распадался на свои составные части так появился ряд звуков,… Поэтому, когда появлялся этот предмет, появлялся как бы сам по себе и в одной и той же форме этот звуковой комплекс.…

Языковой материал для испанского языка
Понятие о смысловой группе. Полныеи редуцированные формы служебных слов. Различное произношение союза y.Интонация полного перечисления. Понятие о… Число единственное и множественное. Падеж общий ипритяжательный.АртикльАртикль… Роль прилагательных.Число. Усеч ннаяформа некоторых прилагательных grande, bueno, malo. Стилистическая рольположения…

Все языки программирования делятся на языки низкого, высокого и сверхвысокого уровня
ОГЛАВЛЕНИЕ ВВЕДЕНИЕ ЗАДАНИЕ ЗАДАНИЕ... ВВЕДЕНИЕ...

Обзор статьи Л.И. Скворцова "Язык общения и культура (экология и язык)"
Автор призывает читателя оберегать русский язык от засорения вульгаризмами и жаргонизмами, ненужными иноязычными заимствованиями, от разного рода… Автор предлагает осмыслить культуру языка в экологическом аспекте – как часть… Писатель придает особое значение предмету лингвистической экологии, которым является культура мышления и речевого…

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

ТЕОРИЯ ЯЗЫКА И ПРОБЛЕМА СУЩЕСТВОВАНИЯ ЯЗЫКА
ББК Р... ОТ СОСТАВИТЕЛЯ...

0.031
Хотите получать на электронную почту самые свежие новости?
Education Insider Sample
Подпишитесь на Нашу рассылку
Наша политика приватности обеспечивает 100% безопасность и анонимность Ваших E-Mail
Реклама
Соответствующий теме материал
  • Похожее
  • По категориям
  • По работам