Язык C# и основы платформы .NET


Чтобы посмотреть этот PDF файл с форматированием и разметкой, скачайте его и откройте на своем компьютере.
Министерство образования Республики Беларусь

Учреждение образования

«Белорусский государственный университет

информатики и радиоэлектроники»


Кафедра информатики






ё.

ё. Волосевич




ЯЗ_К

C# И ОСНОВ_ ПЛёТФОРМ
_ .NET






Курс лекций

для студентов
специальности

1
-
4P P1 P3 Информатика и те2нологии программирования

















Минск 2P1
2



2

Содержание

1. Общая 2арактеристика платформы .NET

................................
.....................

3

2. Общие концепции синтаксиса языка C#

................................
.......................

5

3. Система типов CLR и языка C#

................................
................................
.....

7

4. Идентификаторы, ключевые слова и литералы

................................
...........

9

5. Выражения и операции

................................
................................
.................

11

6. Операторы

................................
................................
................................
......

14

7. Начальные сведения о массива2

................................
................................
..

18

8. Классы

................................
................................
................................
............

21

9. Методы

................................
................................
................................
...........

24

1P. Свойства и инд
ексаторы

................................
................................
.............

28

11. Статические элементы и методы расширения

................................
.........

32

12. Конструкторы и инициализация объектов

................................
...............

34

13. Наследование классов

................................
................................
.................

37

14.
Класс
System
.
Object

и иерар2ия типов
................................
......................

40

15. Структуры

................................
................................
................................
....

43

16. Перечисления

................................
................................
...............................

45

17. Интерфейсы

................................
................................
................................
.

46

18. Универсаль
ные шаблоны

................................
................................
...........

48

19. Использование универсальны2 шаблонов

................................
................

54

20.
Делегаты

................................
................................
................................
.......

56

21. ёнонимные методы и лямбда
-
выражения

................................
................

60

22. События

................................
................................
................................
........

62

23.
Перегрузка

операций

................................
................................
..................

66

24. ёнонимные типы

................................
................................
.........................

69

25. Пространства имѐн

................................
................................
......................

70

26. Генерация и обработка исключительны2 ситуаций

................................

71

27. Директивы препроцессора

................................
................................
.........

74

28. Документирование ис2одного кода
................................
...........................

75

Литература

................................
................................
................................
.........

78



3

1. О
бщая 2арактеристика

платформы

.NET

В середине 2PPP года ко
мпания

Microsoft

объявила о работе над новой
платформой для создания программ, которая получила имя
платформа .NET

(.NET
Fra
mework
). Платформа .NET образует каркас, включающий среду и
сM
полнения, библиотеку классов и
набор
те2нологи
й

для построения приложений
и служб. Основным инструментом разработки для платформы .NET является
интегрированная среда
Microsoft

Visual

Studio
.

1.1.
Инфраструктура платформы .NET

Основой платформы .NET является
общеязыковая среда исполнения

(
Common

Language

Runtime
, CLR). CLR работает как «прослойка» между оп
еM
рационной системой и программой для платформы .NET. Каждая программа
для .NET состоит из одной

или нескольки2 сборок.
Сборка

(
assembly
) является
результатом компиляции ис2одны2 текстов на некотором языке программир
оM
вания для платформы .NET и содержит метаданные и код на
Common

Interm
e-
diate

Language
.
Метаданные



это информационные таблицы с полным опис
аM
нием все2 типов, размещѐнны2 в сборке.
Common

Intermediate

Language

(CIL
или IL)


внутренний язык платформы .NET, он не зависит от типа процессора.
В процессе работы программы CIL компилируется в машинный код сп
ециал
ьM
ным
JIT
-
компилятором

(
Just
-
in
-
Time

compiler
).


Рис. 1. Компиляция и выполнение программ для платформы .NET.

4

Основная задача CLR


это манипулирование сборками: загрузка, JIT
-
компиляция, создание окружения для выполнения сборок. Важной функцией
CLR является управление памятью при работе приложения и выполнение
а
вM
томатической сборки мусора
, то есть фонового освобо
ждения неиспользуемой
памяти. Кроме этого, CLR реализует в приложения2 для .NET проверку типов,
управление политиками безопасности при доступе к коду и другие функции.

В состав платформы .NET в2одит обширная библиотека классов
Fram
e-
work

Class

Library

(FCL)
. "астью этой библиотеки является базовый набор
классов
Base

Class

Library

(BCL), в который в2одят классы для работы со стр
оM
ками и коллекциями данны2, для поддержки многопоточности и множество
други2 классов.
В

FCL
также в2одят

компоненты, поддерживающие р
азличные
те2нологии обработки данны2 и организации взаимодействия с пользователем.
"то классы для работы с XML и базами данны2, для создания пользовательски2
интерфейсов.

В стандартную поставку платформы .NET включено несколько компил
яM
торов. "то компилятор
ы языков C#, F#,
Visual

Basic

.NET, C++/CLI. Благодаря
открытым спецификациям компиляторы для .NET предлагаются различными
сторонними производителями. Необ2одимо подчеркнуть, что любой язык для
платформы .NET является вер2ним элементом ар2итектуры. Имена э
лементов
библиотеки FCL не зависят от языка программирования. Специфичной частью
языка остаѐтся только синтаксис. "тот факт упрощает межъязыковое взаим
оM
действие, перевод текста программы с одного языка на другой. Конечно, в си
нM
такси
се

любого языка программ
ирования для .NET неизбежно на2одит своѐ о
тM
ражение тесная связь с CLR.

Для поддержки межъязыкового взаимодействия служат две спецификации
платформы .NET.
Общая система типов

(
Common

Type

System
, CTS) описыв
аM
ет набор типов, который должен поддерживаться
любым языком программир
оM
вания для .NET.
Общеязыковая спецификация

(
Common

Language

Specification
,
CLS)


это общие правила поведения для все2 .NET
-
языков.

1.2. Версии платформы .NET

Компанией

Microsoft

было выпущено несколько
версий платформы .NET
.
В ф
евра
л
е

2PP2 года
вышла

первая официальная версия

.NET
Framework
.

Затем,
в а
прел
е

2PP3 года
была
опубликована версия 1.1 (пакет обновлений для ве
рM
сии

1.0).

Ноябрь 2PP5 года
ознаменовался выпуском

верси
и

2.P, содержащ
ей

обновлѐнную CLR с поддержкой универсальны2

шаблонов (
generics
). В синта
кM
сис языков C# и VB.NET были внесены изменения

для поддержки шаблонов
, а
также

улучшены те2нологии ASP.NET и ADO.NET.

В

н
оябр
е

2006
года
, вместе
с выпуском операционной системы
Windows

Vista
,

вышла

третья версия
пла
тM
форм
ы

.NET
,
которая

содерж
ала

те2нологии

Windows

Presentation

Foundation
,
Windows

Communication

Foundation
,
Workflow

Foundation
.

В н
оябр
е

2PP7 года
вышла
платформ
а

.NET

3.5, основными особенностями которой являются ре
аM
лизация те2нологии LINQ и новые компилятор
ы

для
C# и VB.NET.

В а
вгуст
е

2PP8 года опубликован пакет обновлений для версии 3.5.

В апреле
2P1P года
5

была

выпущена четвѐртая версия платформы .NET, которая содержит перер
аM
ботанную CLR, а также интегрирует множество новы2 те2нологий, существ
оM
вавши2 ранее в виде

отдельны2 проектов (например,
Parallel

Task

Library
, DLR,
ASP.NET MVC).

Табл. 1 поясняет соотношение между версиями платфо
рM
мы

.NET,
версиями
CLR и
версиями
языка C#.

Таблица 1

Версии платформы .NET, CLR и языка C#

Год выпуска

2002

2003

2005

2006

2007

2008

2010

Версия .NET

1.0

1.1

2.0

3.0

3.5

3.5 SP1

4.0

Версия CLR

1.0

1.1

2.0

4.0

Версия C#

1.0

2.0

3.0

4.0

2.
Общие концепции синтаксиса языка

C#

Специально для платформы .NET был разработан новый язык программ
иM
рования C#. "тот язык сочетает простой
синтаксис, по2ожий на синтаксис яз
ыM
ков C++ и
Java
, и полную поддержку все2 современны2 объектно
-
ориентированны2 концепций и под2одов. В качестве ориентира при разработке
языка было выбрано безопасное программирование, нацеленное на создание
надѐжного и про
стого в сопровождении кода. Здесь и далее рассматривается
синтаксис четвѐртой версии языка C#, доступной в составе .NET
Framework

4
.0
.

Ключевыми структурными понятиями в языке C# являются
программы
,
сборки
,
пространства имѐн
,
пользовательские типы
,

элемент
ы типов
. Ис2о
дM
ный код программы на языке C# размещается в одном или нескольки2 текст
оM
вы2 файла2, имеющи2 стандартное расширение
.cs
. В программе объявляются
пользовательские типы, которые состоят из элементов. Примерами пользов
аM
тельски2 типов являются клас
сы и структуры, а примером элемента типа


м
еM
тод класса. Типы могут быть логически сгруппированы в пространства имѐн, а
физически (после компиляции)


в сборки, представляющие собой файлы с
расширением
.exe

или
.dll
.

Ис2одный
код

программы на языке C#


эт
о набор
операторов

(
statements
)
,
директив

препроцессора

и
комментариев
.
Операторы языка
C
#
и
допустимые директивы

препроцессора

подробно будут рассмотрены далее.
Комментарии игнорируются при компиляции и бывают
дву2

видов:

1.
Однострочный
комментарий



это

комментарий, начинающийся с п
оM
следовательности
//

и продолжающийся до конца строки.

2.
Блочный

(многострочный)

комментарий



все символы, заключѐнные
между

парами

/*

и
*/
.

В C# различаются строчные и прописные символы при записи идентиф
иM
каторов и ключевы2

слов. Количество пробелов в начале строки, в конце стр
оM
ки и между элементами строки значения не имеет. "то позволяет улучшить
структуру ис2одного
кода

программы


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

6

Рас
смотрим простейшую программу на языке C#, которая переводит ра
сM
стояние в миля2 в километры.

using

System;


class

FirstProgram

{


static

void

Main()


{


Console
.Write(
"Input miles: "
);


string

s =
Console
.ReadLine();


double

miles =
double
.Parse(s);


Console
.Write(
);


Console
.WriteLine(miles * 1.609);


}

}

Программа представляет собой описание пользовательского типа


класса

с именем
FirstProgram
. Необязательная директива
using

в первой строке пр
оM
граммы служит для ссылки на пространство имѐн
System
, группирующее баз
оM
вый набор классов. Использование
using

System

позволяет вместо полного
имени класса
System.
Console

записать короткое имя
Console
.

Любая исполняемая программа на C# д
олжна иметь специальную
точку
в2ода
, с которой начинается выполнение
программы
. Такой точкой в2ода вс
еM
гда является метод
Main()

с модификатором
static
, объявленный в некотором
пользовательском типе программы (в данном случае


в классе
FirstProgram
).
Метод

Main()

начинается с вызова метода
Write()

класса
Console
. Методы
Console
.WriteLine()

и
Console
.Write()

выводят информацию на экран, а метод
Console
.ReadLine()

ожидает ввод пользователя и возвращает введѐнные да
нM
ные как строку. Информация со2раняется в лок
альной строковой переменной
s
.
Метод
double
.Parse()

выполняет преобразование строки в вещественный тип.

Если программа содержится в файле
FirstProgram.cs
, то она может быть
скомпилирована при помощи
компилятора командной строки

csc.exe
. При
этом допустимо
указание различны2 параметров
и опций
(например,
имени
итоговой сборки
, ссылок на

необ2одимые сборки и так далее
)
:

csc
.
exe

FirstProgram
.
cs

После компиляции будет получена сборка
FirstProgram.exe
, готовая для
запуска на любом компьютере с установленной плат
формой .NET.

Скомпилированные программы для платформы .NET допускают
декомп
иM
ляцию
, то есть восстановление ис2одного кода программы. Для этой цели мо
жM
но использовать такие инструменты как
ILDasm

(от
Microsoft
)
,
Reflector

или
ReSharper 6 Bundles Decompiler
.

7

3.
Система типов
CLR
и языка
C#

Основой
платформы .NET
является развитая система типов.
CLR испол
ьM
зует уникальное имя типа, обычной составляющей которого является указание
на пространство имѐн. Так, для представления строк служит тип
System.
String
,
где
Sys
tem



название пространства имѐн. Для наиболее распространѐнны2 т
иM
пов платформы .NET язык C# предлагает короткие имена
-
псевдонимы. Напр
иM
мер, тип
int

в C#


это псевдоним типа
System.
Int32
, тип
string



псевдоним
типа
System.
String
, и так далее.

Таблица
2

С
опоставление типовы2 псевдонимов
C
#
и

типов
CLR

Тип
C#

Имя типа в
CLR

Примечание

sbyte

System.
SByte

Знаковые ц
елочисленные типы

short

System.
Int16

int

System.
Int32

long

System.
Int64

byte

System.
Byte

Беззнаковые целочисленные типы

ushort

System.
UInt16

uint

System.
UInt32

ulong

System.
UInt64

char

System.
Char

float

System.
Single

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

double

System.
Double

decimal

System.
Decimal

Тип данны2 повышенной точности

bool

System.
Boolean

Тип для 2ранения логически2 значений

T?

System.
Nullable
T&#x-3T7;

Тип значений
T

с поддержкой
null

(
например,
int
?
)


string

System.
String

Тип для представления строк

object

System.
Object

Базовый тип

dynamic

System.
Object

Динамический тип с проверкой элементов при выпо
лM
нении программы

Все типы платформы
.

можно разделить на
типы значений

(
value

types
)

и
ссылочные типы

(
reference

types
).
Переменная типа значения непосре
дM
ственно
содержит данные
.
К типам значений относятся

структуры

и

перечи
сM
ления
.

Структуры включают
пользовательские
структуры
,
простые типы

(
это
числовые типы

и
тип
bool
)

и
типы с поддержкой
null
.

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

Ссылочные типы


это
класс
ы
,
интерфейс
ы
,
строк
и
,
массив
ы
,
делегат
ы

и
тип
object
.

С точки зрения компилятора языка C# типы можно разделить на
прим
иM
тивные типы

и
пользовательские типы
. Поддержка примитивны2 типов
обе
сM
печена

компилятор
ом
, такие типы не нуждаются в дополнительном объявл
еM
нии.
Простые
типы

и и2 варианты с поддержкой
null
,
а также типы
string
,
o
b-
ject

и
dynamic

принято относить к примитивным типам
.

Пользовательские т
иM
пы перед применением должны быть описаны при помощи особы2 синтаксич
еM
8

ски2 конструкций. Любая программа представляет собой н
абор
определ
е
ни
й

пользовательски2 типов.


Рассмотрим некоторые типы

подробнее
.
"исловые типы делятся на
цел
оM
численные типы
,
типы с плавающей запятой

(удовлетворяют стандарту
IEEE

754)
и
тип
decimal

(
96
бит для 2ранения основания, 1 бит для 2ранения
знака, 8 бит


число от P до 28, позиция запятой в основании справа).
Инфо
рM
мация о числовы2 типа2 представлена в табл.
3
.

Таблица
3

"исловые типы

Категория

Тип

C#

Размер

(бит)

Диапазон

и т
очность

Знаковые цел
оM
численные типы

sbyte

8

-
128..127

short

16

-
32 768..32 767

int

32

-
2 147 483 648..2 147 483 647

long

64

-
9

223

372

036

854

775

808..
9

223

372

036

854

775

807

Беззнаковые ц
еM
лочисленные
типы

byte

8

0..255

ushort

16

0..65535

char

16

Символ в кодировке
UTF
-
16

uint

32

0..4 294 967 295

ulong

64

0..18 446 744 073 709 551 615

Типы с

плава
юM
щей

запятой

float

32

От

±
1.5×1P
-
45

до
±
3.4×1P
38
,

т
очность

7 цифр

double

64

О
т
±
5.P×1P
-
324

до
±
1.7×1P
308
,


точность
15 цифр

Тип
decimal

decimal

128

От

±
1.P×1P
-
28

до
±
7.9×1P
28
,

точность
28 цифр

Отметим
,
что

типы

sbyte
,
ushort
,
uint
,
ulong

не

соответствуют

Common
Language Specification.
"то означает, что данные типы не следует использовать
в интерфейса2 межъязыкового взаимодействия
.

Так как язык
C#


это язык со строгой типизацией, необ2одимо соблюдать
соответствие типов при присваивании и вызове методов. В случае несоотве
тM
ствия выполняется
преобразование типов
, которое бывает явным и неявным.
Для
явного преобразования

(
explicit

conversion
) служит

операция приведения в
форме
(
целевой
-
тип
)
выражение
. При этом ответственность за корректность пр
еM
образования возлагается на программиста.
Неявное преобразование

(
implicit

conversion
) не требует особы2 синтаксически2 конструкций и осуществляется
компиляторо
м. Подразумевается, что неявное преобразование безопасно, то
есть, например, для целочисленны2 типов не проис2одит переполнения. Для
числовы2 типов определено неявное преобразование типа
A

в тип
B
, если на
рис.

2 существует путь из

узла

A

в

узел

B
.


Рис.
2
. С2ема неявного преобразования числовы2 типов.

9

Тип
char

представляет символ в Unicode
-
кодировке

UTF
-
16
.

Тип
char

пр
еM
образуется в типы
sbyte
,
short
,
byte

явно, а в остальные числовые типы


нея
вM
но. Преобразование числового типа в тип
char

может быть выполнено только в
явной форме.

Тип
bool

служит для 2ранения
логически2 (
булевы2
)

значений. Переме
нM
ные данного типа могут принимать значения
true

или
false
.
Ни неявное, ни
явное преобразование
bool

в числовые типы и обратно

невозможно
.

Тип
stri
ng

используется для работы со строками и является последов
аM
тельностью
Unicode
-
символов.

Тип
object



это ссылочный тип, переменной которого можно присвоить
любое значение.

Опишем функциональность, которой обладают пользовательские типы.

1. Класс


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

2. Структура


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

3
. Интерфейс


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

4. Массив


пользовательский тип для представления упорядоченного
набора значений.

5. Перечисление


тип, содержащий в качестве членов
именованные цел
оM
численные константы.

6. Делегат


пользовательский тип, инкапсулирующий метод.

4. Идентификаторы, ключевые слова и литералы

Идентификатор



это пользовательское имя для переменной, константы,
метода или типа. В C# идентификатор


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

При записи идентификатора допустимо использ
оM
вать четыре
шестнадцатеричн
ы2 цифры
кода U
TF
-
16

с префиксом
\
u
.

Идентификатор должен быт
ь уникальным внутри области видимости. Он
не может совпадать с ключевым словом языка, за исключением того случая, к
оM
гда используется специальный
префикс
@

(не являющийся частью идентифик
аM
тора). Примеры допустимы2 идентификаторов:
Temp
,
_variable
,
_

(
символ

по
дM
чѐркивания)
,
@class

(используется префикс
@
, так как
class



ключевое слово)
,
cl
\
u
0061
ss

(
применяется

код
U
TF
-
16

для символа
a
, этот идентификатор совп
аM
дает с идентификатором
@class
)
.

Ключевые слова



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

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

abstract

as

base

bool

break

10

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


sbyte

sealed

short

sizeof

stackalloc

static

string

struct

switch

this

throw

true

try

typeof

uint

ulong

unchecked

unsafe

ushort

using

virtual

void

volat
ile

while

Во

втором

списке

перечислены

контекстные ключевые слова
.
Они

имеют

особое

значение

только

в

ограниченном программном контексте и могут и
сM
пользоваться в качестве идентификаторов за пределами этого контекста, 2отя
так поступать не рекомендуется
.

add

alias

ascending

assembly

by

descending

dynamic

equals

field

from


global

group

into

join



module

on

orderby

param

partial

property

remove

select


type

typevar

value

var

where

yield

Литерал



это

последовательность

символов
,
которая

может

интерпрет
иM
роваться как значение
определѐнного

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

данные.

Рассмотрим правила записи некоторы2 литерало
в. Для ссылочны2 типов
определѐн литерал
null
, который указывает на неинициализированную ссылку.
В языке C# два булевы2 литерала:
true

и
false
. Целочисленные литералы м
оM
гут быть записаны в десятичной или шестнадцатеричной форме. Признаком
шестнадцатеричног
о литерала является префикс
0x

(или
0
X
)
. Конкретный тип
целочисленного литерала определяется следующим образом:



Если литерал не имеет суффикса, то его тип


это первый из типов
int
,
uint
,
long
,
ulong
, который способен вместить значение литерала.



Если литерал имеет суффикс
U

или
u
, его тип


это первый из типов
uint
,
ulong
, который способен вместить значение литерала.

11



Если литерал имеет суффикс
L

или
l
, то его тип


это первый из типов
long
,
ulong
, который способен вместить значение литерала
1
.



Если литерал имеет суффикс
UL
,
Ul
,
uL
,
ul
,
LU
,
Lu
,
lU
,
lu
, его тип


ulong
.

Если в числе с десятичной точкой не указан суффикс, то подразумевается
тип
double
. Суффикс
f

(или
F
) используется для указания на тип
float
, суффикс
d

(или
D
) используется для явного указания на тип
double
, суффикс
m

(или
M
)
определяет литерал типа
decimal
. "исло с плавающей точкой может быть зап
иM
сано в научном формате:
3.5E
-
6
,
-
7
e
10
,
.6E+7
.

Символьный литерал записывают
в
одинарны2
кавычка2
. Обычно это
ед
иM
нич
ный символ (
например,
'a'
).
Допустимо указать четыре
шестнадцатери
чM
н
ы2 цифры
кода U
TF
-
16

с префиксом
\
u

(
'
\
u005C'



это символ
'
\
'
). Можно
использовать префикс
\
x

и
не более

четырѐ2
шестнадцатеричн
ы2 цифр
кода
U
TF
-
16

(
'
\
x
5
c
'



это
тоже
символ
'
\
'
)
. Кроме
этого, для представления нек
оM
торы2 специальны2 символов используются следующие пары:

\
'



одинарная кавычка

\
f



новая страница

\
"



двойная кавычка

\
n



новая строка

\
\



обратн
ая

косая черта

\
r



возврат каретки

\
0



символ с кодом
ноль

\
t



горизонтальн
ая табуляция

\
a



звуковой сигнал

\
v



вертикальная табуляция

\
b



забой

Для строковы2 литералов в языке C# существуют две формы. Обычно
строковый литерал записывается как последовательность символов в двойны2
кавычка2. Среди символов строки могут быть и у
правляющие последовател
ьM
ности (
"This is
a

\
t tabbed string"
).
Дословная форма

(verbatim form) стр
оM
кового литерала


это запись строки в кавычка2 с использованием префикса
@

(
@"There is
\
t no tab"
). В этом случае управляющие последовательности во
сM
принимаютс
я как обычные пары символов. Дословная форма может занимать
несколько строк.

5. Выражения и операции

Любое
выражение

в языке C# состоит из
операндов

и
операций
. Следу
юM
щий список содержит допустимые операции. Они разбиты на группы. Порядок
групп соответству
ет приоритету операций.

1. П
ервичные операции

x.m




Доступ к элементу типа

x(...)



Вызов методов и делегатов

x[...]



Доступ к элементу массива или индексатора

x++




Пост
-
инкремент

x
--




Пост
-
декремент




1

При записи целочисленны2 литералов не рекомендуется использовать суффикс
l

(строчная
латинская буква
L
), так как его легко перепутать с единицей.

12

new

T(...)


Создание объекта или делегата

new

T(...){...}

Создание объекта
с инициализацией

new

{...}


Инициализация объекта анонимного типа

new

T[...]


Создание массива с элементами типа
T

typeof
(T)


Получение для типа
T

объекта
System.
Type

checked
(x)


Вычисление в контролируемом контексте

unchecked
(x)


Вычисление в неконтролируемом контексте

default
(T)


Получение
значения по умолчанию
для типа
T

delegate

{...}

Описание анонимного метода

2.
Унарные операции

+x




Идентичность

-
x




Отрицание

!x




Логическое отрицание

~x




Битовое отрицание

++x




Пре
-
инкремент

--
x




Пре
-
декремент

(T)x




Явное преобразование
x

к типу
T

sizeof
(T)


Размер в байта2 для типа значения
T

3.
Мультипликативные операции

x * y



Умножение

x / y



Деление

x % y



Вычисление остатка

4.
ёддитивные операции

x + y



Сложение
чисел
, сцепление

строк

и делегатов

x


y



Вычитание

5.
Операции сдвига

x y



Битовый сдвиг влево

��x y



Битовый сдвиг вправо

6.
Операции отношения и проверки типов

x y



Меньше

�x y



Больше

x = y



Меньше или равно

�x = y



Больше или равно

x
is

T



Возвращает
true
, если
x

приводим к типу
T

x
as

T



Возвращает
x
, приведѐнный к типу
T
, или
null

7.
Операции равенства

x == y



Равно

x != y



Не равно

8.
Логическ
ое
AND

x & y



Целочисленное битовое AND,
булево

AND


13

9
.
Логическ
ое
XOR

x ^ y



Целочисленное битовое XOR,
булево

XOR

10
.
Логическ
ое
OR

x | y



Целочисленное битовое OR,
булево

OR

11
. Условное
AND

x && y



Вычисляется
y
, только если
x

равно

true

1
2. Условное
OR

x || y



Вычисляется
y
, только если
x

равно
false

13.

Операция проверки на
null

x ?? y



Возвращает
x
, если
x

не равно
null
. Иначе возвращает
y

1
4
. Условие

x ? y : z


Если
x

равно
true
, вычисляется
y
, иначе
z

15.
Операции присваивания

и лямбда
-
выражений

x = y



Присваивание

x op= y



Составное присваивание,
поддерживаются операции






*=

/=

%=

+=

-
=

=

��=

&=

^=

|=

�x =
code


Описывает

блок кода
code

Поясним использование некоторы2 операций. Для контроля значений, п
оM
лучаемы2 при работе с числовыми выражениями, в C# предусмотрено испол
ьM
зование контролируемого и неконтролируемого контекстов.
Контролируемый
контекст

объявляется в форме
checked

операторный
-
блок
, либо как операция
checked
(
выражение
)
. Если при вычислении в контролируемом контексте пол
уM
чается значение, вы2одящие за пределы цел
евого типа, то генерируется либо
ошибка компиляции (для константны2 выражений), либо обрабатываемое и
сM
ключение (для выражений с переменными).
Неконтролируемый контекст

об
ъM
является в форме
unchecked

операторный
-
блок
, либо как операция
unchecked
(
выражение
)
.
При использовании неконтролируемого контекста вы2од
за пределы целевого типа ведѐт к автоматическому «урезанию» результата либо
путѐм отбрасывания бит (целые типы), либо путѐм округления (вещественные
типы). Неконтролируемый контекст применяется в вычислен
ия2 по умолчанию.

ёрифметические операции
+
,
-
,
*
,
/
,
%

определены для все2 числовы2 т
иM
пов, за исключением 8
-

и 16
-
битовы2 целы2 типов. Для коротки2 целы2 типов
компилятор выполняет неявное преобразование типов (при этом операция с ц
еM
лыми числами должна ос
таться операцией с целыми числами). ёрифметич
еM
ские операции для типов с плавающей запятой не генерируют исключительны2
ситуаций при переполнении, потере точности или делении на ноль. В
таки2
случая2

получаются особые значения, определѐнные в виде констант
double
.NaN
,
double
.NegativeInfinity
,
double
.PositiveInfinity

(т.е. «не чи
сM
ло», «минус бесконечность», «плюс бесконечность»).

14

6. Операторы

Методы пользовательски2 типов состоят из операторов, которые выпо
лM
няются последовательно. "асто используется
операторн
ый блок



последов
аM
тельность операторов, заключѐнная в фигурные скобки.

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

К
операторам объявления
относятся
операторы объявления переменны2

и
операторы объявления констант
. Для объявления локальны2 переменны2 м
еM
тода применяется опер
атор следующего формата:

тип имя
-
переменной [= начальное
-
значение]
;

Здесь
тип



тип переменной,
имя
-
переменной



допустимый идентификатор,
необязательное
начальное
-
значение



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

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

int

a
;
// простейший вариант объявления

int

a

= 20;
// объявление с инициализацией

int

a
,
b
,
c
;
// объявление однотипных переменных

int

a

= 20,
b

= 10;
// инициализация нескольких переменных

Локаль
ная переменная может быть объявлена без указания типа, с испол
ьM
зованием ключевого слова
var
. В этом случае компилятор выводит тип пер
еM
менной из обязательного выражения инициализации.

var

x = 3;

var

y =
"Student"
;

var

z

=
new

Student
();

Не стоит воспринимать переменные, объявленные с
var
, как некие униве
рM
сальные контейнеры для данны2 любого типа. Все эти переменные строго т
иM
пизированы. Так, переменная
x

в приведѐнном
выше
примере имеет тип
int
.

Оператор объявления константы имеет следующи
й синтаксис:

const

тип
-
константы имя
-
константы
=
выражение
;

Допустимый
тип
-
константы



это
числовой тип, тип
bool
,
тип
string
,
пер
еM
числение

или произвольный ссылочный тип.

выражение
, которое присваивается
константе, должно быть полностью вычислимо на момент компиляции. Обычно
в качестве выражения используется литерал
соответствующего типа
. Для сс
ыM
лочны2 типов (за исключением
string
) единственно допустимым выражением
является
null
.

Ка
к и при объявлении переменны2, можно определить в одном
операторе несколько однотипны2 констант
:

15

const

double

Pi

= 3.1415926
, E = 2.7182
8
182
8
;

const

string

Name

=
"Student"
;

const

object

locker

=
null
;

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

{


int

i

= 10;

}

Console
.
WriteLine
(
i
);
// ошибка компиляции, переменная i не доступна

Если операторные блоки вложены друг в друга, то внутренний блок не
может содержать объявлений переменны2, идентификаторы кот
оры2 совпад
аM
ют с переменными внешнего блока:

{


int

i = 10;


{


int

i = 20;
//
ошибка

компиляции


}

}

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

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



это выражения, одновременно являющиеся д
оM
пустимыми операторами:



операция присваивания
(включая инкремент и декремент);



операция вызова метода или делегата;



операция создания объекта.

Приведѐм несколько примеров:

x

= 1 + 2;
// присваивание

x
++;
// инкремент

Console
.
Write
(
x
);
// вызов метода

new

StringBuilder
();
// создание объекта

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

6.3. Операторы пере2ода

К
операторам пере2ода

относятся
break
,
continue
,
goto
,
,
throw
.
Оператор
break

используется для вы2ода из операторного блока циклов и оп
еM
ратора
switch
. Оператор
break

выполняет пере2од на оператор за блоком. Оп
еM
ратор
continue

располагается в теле цикла и применяется для запуска новой
итерации цикла. Если циклы вложен
ы, то запускается новая итерация того ци
кM
ла, в котором непосредственно располагается
continue
.

Оператор
goto

передаѐт управление на помеченный оператор. Обычно
данный оператор употребляется в форме
goto

метка
, где
метка



это допуст
иM
16

мый идентификатор. Метк
а должна предшествовать помеченному оператору и
заканчиваться двоеточием, отдельно описывать метки не требуется:

goto

label
;

. . .

label
:

A

= 100;

Оператор
goto

и помеченный оператор должны
располагаться

в одном
операторном блоке. Возможно использование оператора
goto

в одной из сл
еM
дующи2 форм:

goto

case

константа
;

goto

default
;

Данные формы обсуждаются при рассмотрении оператора
switch
.

Оператор

служит для завершения методов. Оператор
throw

генер
иM
рует исключительную ситуацию

(
р
абота с методами и исключительными сит
уM
ациями рассматривается далее)
.

6.4. Операторы выбора

Операторы выбора



это операторы
if

и
switch
. Оператор
if

в языке C#
имеет следующий
синтаксис
:

if

(
условие
)


вложенный
-
оператор
-
1

[
else


вложенный
-
оператор
-
2
]

Здесь
условие



это некоторое булево выражение
,
вложенный
-
оператор



оператор
(за исключением оператора объявления)
или операторный блок
. Ветвь
else

является необязательной.

Оператор
switch

выполняет одну из групп инструкций в зависимости от
значения тестируемого выражения. Синтаксис оператора
switch
:

switch

(
выражение
)

{


case

константное
-
выражение
-
1
:


операторы


оператор
-
перехода


case

константное
-
выражение
-
2
:


операторы


оператор
-
перехода


. . .


[
default
:


операторы


оператор
-
перехода]

}

17

Тестируемое
выражение

должно возвращать значение целочисленного типа
(включая
char
), булево значение, строку или элемент перечисления. При совп
аM
дении тестируемого и константного выражений выполняется соответствующая
ветвь
case
. Если совпадения не обнаружено, то выполняется ветвь
default


сM
ли она есть).
оператор
-
перехода



это один из следующи2 операторов:
break
,
goto
,
,
throw
. Оператор
goto

используется с указанием либо ветви
d
e-
fault

(
goto

default
), либо определѐнной ветви
case

(
goto

case

константное
-
выражение
).

Хотя после
case

может быть указано только одно константное выражение,
при необ2одимости несколько ветвей
case

можно сгруппировать с
ледующим
образом:

switch

(n)

{


case

0:


case

1:


case

2:


. . .

}

6.5. Операторы циклов

К
операторам циклов

относятся операторы
for
,
while
,
do
-
while
,
foreach
.
Для циклов с известным числом итераций используется оператор
for
:

for

(
[инициализатор]
;
[условие]
;
[итератор]
)
вложенный
-
оператор

Здесь
инициализатор

задаѐт начальное значение счѐтчика (или счѐтчиков)
цикла. Для счѐтчика может использоваться существующая переменная или
объявляться новая переменная, время жизни которой будет о
граничено циклом
(при этом вместо типа переменной допустимо указать
var
). Цикл выполняется,
пока булево
условие

истинно, а
итератор

определяет изменение счѐтчика цикла
на каждой итерации.

Простейший пример использования цикла
for
:

for

(
int

i = 0; i 10; i
++)
// i
доступна

только

в

цикле

for


Console
.
WriteLine
(
i
);
// вывод чисел от л до ц

В инициализаторе можно объявить и задать начальные значения для н
еM
скольки2 счѐтчиков одного типа. В этом случае итератор может представлять
собой
последовательность из нескольки2 операторов, разделѐнны2 запятой:

// цикл выполнится т раз, на последней итерации i = с, j = у

for

(
int

i = 0, j = 10; i j; i++, j
--
)


Console
.WriteLine(
"i = {0}, j = {1}"
, i, j);

Если число итераций цикла заранее неизв
естно, можно использовать цикл
while

или цикл
do
-
while
. Данные циклы имеют с2ожий синтаксис:

18

while

(
условие
)
вложенный
-
оператор


do


вложенный
-
оператор

while

(
условие
);

В обои2 оператора
2

цикла тело цикла выполняется, пока булево
условие

истинно. В цикле
while

условие проверяется в начале очередной итерации, а в
цикле
do
-
while



в конце. Таким образом, цикл
do
-
while

всегда выполнится, по
крайней мере, один раз. Обратите внимание
:

условие

должно присутствовать
обязательно. Для организации
бесконечны2 циклов на месте условия можно
использовать литерал
true
:

while

(
true
)
Console
.WriteLine(
"Endless loop"
);

Для перебора элементов объектов перечисляемы2 типов (например, масс
иM
вов) в C# существует специальный цикл
foreach
:

foreach

(
тип

идентификат
ор

in

коллекция
)
вложенный
-
оператор

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

6.6. Прочие операторы

К группе прочи2 операторов относятся операторы
lock

и
using
. Первый
связан с син2ронизацией потоков выполнения, второй


с процессом освобо
жM
д
ения локальной переменной
1
. Подробно синтаксис и примеры использования
данны2 операторов рассмотрены в соответствующи2 раздела2.

7. Начальные сведения о массива2

Массивы



это ссылочные пользовательские типы. Объявление массива в
C# с2оже с объявлением пер
еменной, но после указания типа размещается
сп
еM
цификатор размерности



пара квадратны2 скобок:

int
[]
data
;

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

тип
[
количество
-
элементов
]
.

int
[] data;

data =
new

int[10];




1

В C# имеется директива
using

для импорта пространств имѐн. Следует различать директ
иM
ву
using

и оператор
usin
g
.

19

Создание массива можно совместить с его объявлением:

int
[]
data

=
new

int
[10];

Созданный массив автоматически заполняется значениями по умолчанию
для своего базового типа (ссылочные типы


null
, числа


0
, тип
bool



false
).

Для доступа к элементу массива указывается имя массива и индекс в ква
дM
ратны2 скобка2:
data[0] = 10
.
Индекс массива должен неявно приводиться к
типам
int
,
uint
,
long

или
ulong
.

"лементы массива нумеруются с нуля, в C# не
пред
усмотрено синтаксически2 конструкций для указания особого значения
нижней границы массива. При вы2оде индекса массива за допустимый диап
аM
зон генерируется исключительная ситуация.

В C# существует способ задания все2 элементов массива при создании.
Для этого

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

int
[] data_1 =
new

int
[4] {1, 2, 3, 5};

int
[] data_2 =
new

int
[] {1, 2, 3, 5};

int
[] data_3

=
new
[] {1, 2, 3, 5};

int
[] data_4 = {1, 2, 3, 5};

Первые три примера инициализации допускают указание вместо типа п
еM
ременной ключевого слова
var

(например,
var

data_3 =
new
[] {1, 2, 3, 5}
).
Компилятор вычислит тип массива автоматически.

При необ2одимости

можно объявить массивы, имеющие несколько ра
зM
мерностей. Для этого в
спецификаторе размерности

помещают запятые, «разд
еM
ляющие» размерности:

// двумерный массив d

int
[,]
d
;

d

=
new

int
[10, 2];


//
трёхмерный

массив

Cube

int
[,,]
Cube

=
new

int
[3, 2, 5];


//
объявим двумерный массив и инициализируем его

int
[,]
c

=
new

int
[2, 4] {


{1, 2, 3, 4},


{10, 20, 30, 40}


};


// то же самое, но немного короче

int
[,]
c

= {{1, 2, 3, 4}, {10, 20,
30, 40}};

20

В приведѐнны2 примера2 объявлялись массивы из нескольки2 размерн
оM
стей. Такие массивы всегда являются прямоугольными. Можно объявить
ма
сM
сив массивов
, используя следующий синтаксис
1
:

int
[][]
table
;
// table


массив одномерных массивов

table

=
new

int
[2][];
// в table будет п одномерных массива

table
[0] =
new

int
[2];
// в первом массиве будет п элемента

table
[1] =
new

int
[20];
// во втором


пл элементов

table
[1][3] = 1000;
// работаем с элементами table


// совместим объявлен
ие и инициализацию массива массивов

int
[][]
t

= {
new
[] {10, 20},
new
[] {1, 2, 3}};

Язык
CIL

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

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

возможно перемещение по массиву в одном
направлении


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

фрагменте кода производится
суммирование элементов массива:

int
[] data = {1, 3, 5, 7, 9};

var

sum = 0;

foreach

(
var

element
in

data)

{


sum

+=
element
;

}

В заключение рассмотрим вопрос о приведении типов массивов. Массивы
ковариантны

для ссылочны2 типов. "то означает

следующее:

если ссылочный
тип
A

неявно приводим к ссылочному типу
B
, массив с элементами типа
A

может
быть присвоен массиву с элементами типа
B
. При этом количество элементов в
массиве роли не играет, но массивы должны им
еть одинаковую размерность.

public

class

Student

{ . . . }
//
объявление

класса


Student
[] students =
new

Student
[10];

object
[]
array

=
students
;
//
ковариантность

массивов

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




1

Объявление
int
[,][]
data

задаѐт двумерный массив, состоящий из одномерны2 массивов.

Иными словами, спецификаторы размерностей читаются слева направо.

21

8. Классы

Класс является основным пользовательским типом. Синтаксис объявления
класса в C# следующий:

модификаторы

class

имя
-
клас
са

{


[элементы
-
класса]

}

8.1. Допустимые элементы класса

1. Поле.

Синтаксис объявления поля класса совпадает с синтаксисом оп
еM
ратора объявления переменной

(
к
ак правило, идентификаторы полей снабж
аM
ются неким
оговорѐнным

префиксом).

Т
ип поля
всегда
должен быть указан я
вM
но, использование
var

не допускается. Если для поля не указано начальное зн
аM
чение, то поле принимает значение по умолчанию для соответствующего типа
(для числовы2 типов


0
, для типа
bool



false
, для ссылочны2 типов


null
).
Для полей

можно примен
ять

модификатор
readonly
, который запрещает изм
еM
нение поля после его начальной установки.

class

Person

{


readonly

int

_age = 20;


string

_
name

=
"
None
"
;

}

Поля с модификатором
readonly

по2ожи на константы, но имеют следу
юM
щие отличия:



т
ип поля может быть любым
;



з
начение поля мож
но

установ
ить

при объявлении
или

в конструкторе
класса
;



з
начение поля вычисляется в момент выполнения, а не
при
компиляции.

2. Константа.

Синтаксис объявления константы в классе аналогичен си
нM
таксису,
применяемому при объявлении константы в теле метода.

Следующие элементы класса будут подробно рассмотрены в дальнейшем.

3. Метод.

Методы описывают функциональность класса.

4. Свойство.

Свойства класса призваны предоставить защищѐнный доступ
к полям.

5. Инд
ексатор.

Индексатор


это свойство
-
коллекция, отдельный элемент
которого доступен по индексу.

6. Конструктор.

Задача конструктора


начальная инициализация объекта

(экземплярный конструктор)

или класса

(статический конструктор)
.

7. Финализатор.

Финализатор

автоматически вызывается сборщиком м
уM
сора и содержит завершающий код для объекта.

8. Событие.

События представляют собой ме2анизм рассылки уведомл
еM
ний различным объектам.

22

9. Операция.

Язык C# допускает перегрузку некоторы2 операций для об
ъM
ектов класса.

10
. Вложенный пользовательский тип.

Описание класса может соде
рM
жать описание другого пользовательского типа


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

8.2. Модификаторы доступа для элементов и типов

Для поддержания принципа инкапсуляции элементы класса могут сна
бM
жаться специальными
модификаторами доступа
:



private
. "лемент с
этим

модификатором доступен только в том типе, в
котором определѐн. Напри
мер, поле доступно только в содержащем его классе.



protected
. "лемент виден в типе, в котором определѐн, и в наследника2
этого типа (даже если наследники расположены в други2 сборка2). Данный м
оM
дификатор может применяться только в типа2, поддерживающи2 н
аследование,
то есть в класса2.



internal
. "лемент доступен без ограничений, но только в той сборке, где
описан.



protected

internal
.
"лемент виден в содержащей его сборке без огран
иM
чений, а вне сборки


только в наследника2 типа (т.е.
это комбинация
pro
tected

или

internal
1
).



public
. "лемент доступен без ограничений как в той сборке, где описан,
так и в други2 сборка, к которым подключается сборка с элементом.

По умолчанию (без указания) для все2 элементов типа применяется мод
иM
фикатор
private
. Для
локальны2 переменны2 методов и операторны2 блоков
модификаторы доступа не используются.

При описании самостоятельного класса допустимо указать для него мод
иM
фикаторы
public

или
internal

(
internal

применяется по умолчанию). Если же
класс вложен в другой поль
зовательский тип, то такой класс можно объявить с
любым модификатором доступа. Заметим, что у
internal
-
класса
public
-
элементы за пределами сборки не видны.

8.3. Разделяемые классы

Хорошей практикой программирования считается размещение каждого
класса в отд
ельном файле. Однако иногда классы получаются настолько бол
ьM
шими, что указанный под2од становится непрактичным. "то часто справедливо
при использовании средств автоматической генерации

код
а
.
Разделяемые кла
сM
сы

(partial classes)


это классы, разбитые на не
сколько фрагментов, описанны2
в отдельны2 файла2 с ис2одным кодом
2
.

Для объявления разделяемого класса используется модификатор
partial
:




1

В CLR
есть

модификатор доступа, соответствующий
protected

и

internal
.
Но п
ри пом
оM
щи языка C# такой
уровень доступа описать нельзя.

2

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

23

//
файл

part1.cs

partial

class

BrokenClass

{


private

int

someField;


private

string

anotherField;

}


//
файл

part2.cs

partial

class

BrokenClass

{


public

void

() { }

}

Все фрагменты разделяемого класса должны быть доступны во время ко
мM
пиляции, так как «сборку» типа выполняет компилятор. Ещѐ одно замечание
касается использования модификаторов,
применяемы2 к классу. Модификат
оM
ры доступа должны быть одинаковыми у все2 фрагментов. Если же к одному из
фрагментов применяется модификатор
sealed

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

8.4.
Использование класса

"тобы использовать класс после объявления (то есть, получить доступ к
его открытым экземплярным элементам
1
), необ2одима переменная класса


объект
. Объект объявляется как обычная переменная:

имя
-
класса

имя
-
объекта
;

Так как класс


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



вызов конструктора класса. Ес
ли ко
нM
структор не описывался, применяется предопределѐнный конструктор без п
аM
раметров с именем класса:

имя
-
объекта

=
new

имя
-
класса
();

Инициализацию объекта можно совместить с его объявлением:

имя
-
класса

(
или
var
)

имя
-
объекта

=
new

имя
-
класса
();

Доступ к экземплярным элементам класса через объект осуществляется по
синтаксису
имя
-
объекта
.
имя
-
элемента
.




1

Для доступа к константе применяется синтаксис
имя
-
класса.имя
-
константы
.

24

9. Методы

Методы

в языке C# являются неотъемлемой частью описания таки2 пол
ьM
зовательски2 типов как класс или структура. В C# не существует глобальны2

методов


любой метод должен быть членом класса или структуры.

9.1. Описание метода

Рассмотрим общий синтаксис описания метода:

модификаторы

тип

имя
-
метода
(
[параметры]
)
тело
-
метода

Здесь
тип



это тип возвращаемого методом значения. Допустимо испол
ьM
зовани
е любого типа. В C# формально не существует процедур


любой метод
считается
функцией. Для «процедуры» в качестве
тип возвращаемого значения

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

Список формальны2 параметров метода



это набор элементов, разделѐ
нM
ны2 запятыми. Каждый элемент имеет следующий формат:

[модификатор]

тип

имя
-
формального
-
параметра

[= значение]

Существуют четыре вида параметров:

1.
Параметры
-
значения



объявляются без модификатора;

2.
Параметры, передаваемые по ссылке



используют модификатор
ref
;

3.
Вы2одные параметры



объявляются с модификатором
out
;

4.

Параметры
-
списки



применяется модификат
ор
params
.

Параметры, передаваемые по ссылке и по значению, ведут себя аналогично
тому, как это проис2одит в други2 языка2 программирования. Вы2одные пар
аM
метры подобны ссылочным, то есть при работе с ними в теле метода не созд
аM
ѐтся копия фактического парам
етра. Компилятор отслеживает, чтобы вы2о
дM
ным параметрам в теле метода обязательно было присвоено значение.

Параметры
-
списки позволяют передать в метод любое количество арг
уM
ментов. Метод может иметь не более одного параметра
-
списка, который обяз
аM
тельно долж
ен быть последним в списке формальны2 параметров. Тип пар
аM
метра
-
списка объявляется как
одномерный
массив, и работа с таким параме
тM
ром проис2одит в методе как с массивом. Каждый аргумент из передаваемого в
метод списка ведѐт себя как параметр, переданный по

значению.

При объявлении метода для параметров
-
значений допустимо указать зн
аM
чение параметра по умолчанию.

"то значение должно быть вычислимо на этапе
компиляции.

Так определяется
опциональный параметр
. Опциональные пар
аM
метры должны быть указаны в конце с
писка формальны2 параметров метода.

Для вы2ода из метода служит оператор

или оператор
throw
. Если
тип метода не
void
, то после

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

или оператор
throw

должны
25

встретиться в таком методе во все2 ветвя2 кода
2отя бы

один раз
.

И
сключение
м
явля
ются ветви метода, для которы2 компилятор распознаѐт бесконечность в
ыM
полнения (например, из
-
за бесконечног
о цикла)
.

Рассмотрим несколько примеров объявления методов.

1. Простейшее объявление метода
-
процедуры без параметров:

void

SayHello()

{


Console
.WriteLine(
"Hello!"
);

}

2. Метод без параметров, возвращающий целое значение:

int


{


Console
.WriteLine(
"Hello!"
);



5;

}

3. Функция
Add()

выполняет сложение дву2 аргументов:

int

Add(
int

a,
int

b)

{



a

+
b
;

}

4. Функция

возвращает 1P как результат своей работы,
кроме этого значение параметра
a

устанавливается р
авным 1PP:

int

out

int

a)

{


a = 100;



10;

}

5.
Метод

PrintList
()

использует

параметр
-
список
:

void

PrintList(
params

int
[] list)

{


foreach
(
int

item
in

list)


Console
.WriteLine(item);

}

6.
Метод

AddWithOptional
()

имеет

опциональн
ый

параметр

y
:

int

AddWithOptional(
int

x,
int

y = 5)

{



x

+
y
;

}

26

В экземплярны2 метода2 доступен параметр
this

(в метода2 класса


тол
ьM
ко для чтения, в метода2 структуры


и для чтения, и для записи). "то ссылка
на текущий экземпляр.
Данную ссылку можно применять для устранения ко
нM
фликта имѐн (если имя элемента типа совпадает с именем параметра метода):

class


{


private

int

age;


private

string

name;



public

void

int

age)


{


this
.
age

=
age

� 0 ?
age

: 0
;


}

}

C# позволяет выполнить

в пользовательски2 типа2

перегрузку методов
.
Перегруженные методы имеют одинаковое имя, но разную сигнатуру.
Сигн
аM
тура



это упорядоченный набор из модификаторов и типов формальны2 пар
аM
метров. При выполнении перегрузки следу
ет учитывать некоторые нюансы.
Например, типы
object

и
dynamic

эквивалентны с точки зрения
CLR
. Значит,
нельзя выполнить перегрузку методов, базируясь только на разнице эти2 типов:

// код не компилируется


методы Foo() нельзя различить при вызове

void

Foo
(
object

a
) { . . . }

void

Foo(
dynamic

a) { . . . }

Если одна версия метода как признак отличия содержит модификатор
ref
,
а другая


out
, то
вызов
метод
ов

также
будет
неразличим с точки
CLR
:

// код не компилируется


методы Foo() нельзя различить при вызове

void

Foo
(
out

int

a
) { . . . }

void

Foo(
ref

int

a) { . . . }

Однако если одна версия метода содержит модификатор
ref

или
out
, а
другая нет, то методы различимы:

//
код

компилируется

void

Foo
(
out

int

a
) { . . . }

void

Foo
(
int

a
) { . . . }

Наконец, е
сли две
версии перегруженного метода различаются только м
оM
дификатором
params
, они считаются неразличимыми:

// код не компилируется


методы Foo() нельзя различить при вызове

void

Foo
(
params

int
[]
a
) { . . . }

void

Foo(
int
[]

a) { . . . }

27

9.2.
Вызов

метода

При
вызове метода на место формальны2 параметров помещаются факт
иM
ческие
аргументы
.
При этом вначале производится вычисление все2 аргуме
нM
тов (если они заданы выражениями). Вычисление аргументов всегда проис2
оM
дит последовательно, слева направо.
Соответствие межд
у параметром и арг
уM
ментом устанавливается либо по позиции, либо используя синтаксис
именова
нM
ны2 аргументов
:

имя
-
формального
-
параметра
:
выражение
-
для
-
аргумента

Рассмотрим примеры вызова метода
Add()
, содержащего три параметра:

int

Add(
int

x,
int

y = 3,
int

z = 5)

{



x

+
y

+
z
;

}


int

res
_1 =
Add
(10, 20, 30);
// передача по позиции

int

res
_2 =
Add
(
x
:10,
z
:20,
y
:30);
// именованные параметры

int

res
_3 =
Add
(10,
z
:20,
y
:30);
// комбинирование двух способов

Использование именованны2
аргументов зачастую необ2одимо, если м
еM
тод содержит опциональные параметры:

int

res
_4 =
Add
(10,
z
:20);

Метод с параметром
-
списком можно вызвать несколькими способами.
Можно
указать в качестве аргумента массив или
передать методу произвольное
(даже нулевое)

количество аргументов
соответствующего
типа
. Во втором сл
уM
чае для 2ранения аргументов будет создан временный массив (это немного
снижает производительность)
:

// переда
ё
м два аргумента

PrintList
(10, 20);

// теперь переда
ё
м четыре аргумента

PrintList
(1, 2,
3, 4);

// созда
ё
м и переда
ё
м массив целых чисел

PrintList
(
new
[] {10, 20, 30, 40});

// можем вообще ничего не передавать

PrintList
();

Если при описании параметра использовались модификаторы
ref

или
out
,
то они должны быть указаны и при вызове. Кроме этого,
фактические аргуме
нM
ты с такими модификаторами должны быть представлены переменными, а не
литералами или выражениями. В случае параметров
-
значений тип аргумента
должен совпадать или неявно приводится к типу формального параметра. При
согласовании типов в сл
учае возникновения двусмысленности делается выбор
числового типа из той же группы знаковости. Например, пусть имеются пер
еM
28

груженные методы
M(
uint

x)

и
M(
int

x)
, а переменная
y

имеет тип
ushort
. Т
оM
гда вызов
M(y)

означает вызов версии с формальным параметром

типа
uint
. Для
ref
-

и
out
-
параметров требуется абсолютное совпадение типов.

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

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

это

следующим

образом
:

public

partial

class

Student

{


partial

void

M(
int

x);

}


public

partial

class

Student

{


partial

void

M(
int

x)


{


Console
.
WriteLine
(
"M body"
);


}

}

Разделяемые методы подчиняются следующим правилам:



о
бъявление метода начинается с модификатора
partial
;



м
етод обязан возвращать значение
void
;



м
етод может иметь параметры, но
out
-
параметры запрещены;



м
етод неявно объявляется как
private

(п
оэтому он

не может быть вирт
уM
альным
)
;



р
азделяемые методы могут быть статическими или универсальными;



в
ызов разделяемого метода нельзя инкапсулировать в делегат.

Отметим ещѐ одну особенность разделяемого метода: его реализация м
оM
жет быть опущена. В этом случае к
омпилятор даже не генерирует код вызовов
разделяемого метода.

1P. Свойства и индексаторы

Свойства

класса призваны предоставить защищѐнный доступ к полям. Как
и в большинстве объектно
-
ориентированны2 языков, в C# непосредственная
работа с полями не приветствуется. Поля класса обычно объявляются
с мод
иM
фикатором

private
, а для доступа к ним используются
свойства.

Рассмотрим базовый синтаксис описания свойства:

модификаторы

тип
-
свойства

имя
-
свойства

{



{

операторы

}



{

операторы

}

}

29

Синтаксис описания заголовка свойства напоминает синтаксис описания
обычного поля. Тип свойства обычно совпадае
т с типом того поля, для обсл
уM
живания которого свойство создаѐтся. У свойства присутствует специальный
блок, содержащий методы для доступа к свойству. Данный блок состоит из
-
части и
-
части, далее называемы2
аксессор

и
мутатор

соответственно. О
дM
на из

частей может отсутствовать, так получается
свойство только для чтения

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

Рассмотрим пример класса, имеющего свойства:

public

class

Student

{


private

int

_age;


private

string

_name;



public

int

Age


{





{





_age;



}




{


_
age

=
value



0 ?
value

:

0
;

//
проверка

корректности


}


}



public

string

Name


{



{

"My name is "

+ _name; }



{ _
name

=
value
; }


}

}

Свойства
транслируются при компиляции в вызовы методов. В скомпил
иM
рованный код класса добавляются методы со специальными именами
Name
()

и
Name
()
, где
Name



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

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


только
из методов того класса, где свойство объявлено. В C# разрешено при описании
свойства указывать модификаторы доступа для аксессоров и мутаторов. При
30

этом действуют два правила. Во
-
первы
2, модификатор может быть только у
одной из частей. Во
-
вторы2, он должен понижать видимость части по сравн
еM
нию с видимостью всего свойства:

public

class

SomeClass

{


public

int

Prop


{



{

0; }


private


{ }


}

}

Достаточно часто свойство содержит только простейший код доступа к
полю. Вот

фрагмент

класса

с

таким

свойством
:

public

class

Person

{


private

string

_name;



public

string

Name


{



{

_name; }



{ _
name

=
value
; }


}

}

"тобы облегчить описание таки2 свойств
-
«обѐрток», в C# имеются
авт
оM
свойства

(auto properties). Используя автосвойства, приведѐнный фрагмент к
оM
да можно переписать следующим образом:

public

class

Person

{


public

string

Name {
;
; }

}

В этом
случае компилятор сам сгенерирует необ2одимое поле класса, св
яM
занное со свойством. Обратите внимание: в автосвойстве должны присутств
оM
вать и часть
, и часть
. При необ2одимости получить аналог классич
еM
ски2 свойств только для чтения необ2одимо использ
овать модификаторы д
оM
ступа для частей:

public

class

Person

{


public

string

Name {
;
private

; }

}

Кроме скалярны2 свойств язык C# поддерживает
индексаторы
. При п
оM
мощи индексаторов осуществляется доступ к коллекции данны2, содержащи2ся
31

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


пары квадратны2 скобок и индекса.

Объявление индексатора напоминает объявление обычного свойства:

модификаторы

тип

this
[
параметры
] {
-
и
-
-
блоки

}

Параметры индексатора служат для

описания типа и имѐн индексов, пр
иM
меняемы2 для доступа к данным. Параметры индексатора могут быть описаны
как параметры
-
значения или как параметр
-
список (с использованием
params
).
Также допустимо использование опциональны2 параметров.

Рассмотрим пример кл
асса, содержащего индексатор. Пусть данный класс
описывает студента с набором оценок:

public

class

Student

{


private

readonly

int
[] _marks =
new

int
[5];



public

int

this
[
int

i]


{





{




Belongs(i, 0, 4) ? _marks[i] : 0;



}




{


if

(Belongs(i, 0, 4) && Belongs(
value
, 1, 10))


_marks[i] =
value
;


}


}



private

bool

Belongs(
int

x,
int

min,
int

max)


{



�(x = m
in) && (x = max);


}

}

При использовании индексатора указывается имя объекта и значение и
нM
декса (или индексов) в квадратны2 скобка2. Допустимы именованные индексы
(по аналогии с именованными аргументами метода). Если необ2одимо испол
ьM
зовать индексатор в предела2 класса, применяют синтаксис
this
[
значение
]
.

var

student =
new

Student
();

student[1] = 8;

student[3] = 4;

for

(
int

i = 0; i 5; i++)

{


Console
.
WriteLine
(
student
[
i
]);

}

32

В одном классе нельзя объявить два индексатора, у котор
ы2 совпадают т
иM
пы параметров. Можно объявить индексаторы, у которы2 параметры имеют
разный тип или количество параметров различается
1
.

11. Статические элементы и методы расширения

Поля, методы и свойства классов, которые рассматривались
ранее
, испол
ьM
зовали
сь при помощи объекта класса. Такие элементы называются экземпля
рM
ными.

Разберѐм употребление статически2 элементов.

11.1. Статические элементы

Статические

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

"тобы объявить статический элемент,

применяется модификатор
static
:

public

class

Account

{


private

static

double

_tax = 0.1;



public

static

double



{



_
tax

* 100;


}

}

Для вызова статически2 элементов требуется использовать имя класса:

Console
.
WriteLine
(
Account
.
());

Подчеркнѐм, что статическими могут быть сделаны поля, методы и обы
чM
ные свойства. Открытая константа, описанная в классе, уже работает как стат
иM
ческий элемент. Индексатор класса не может быть статическим
2
.

11.2. Статические классы

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

определяется

статический

класс
:

public

static

class


{


public

static

string

BaseDir { }


public

static

string

() { }

}




1

Индексаторы транслируются в методы с именами
get_Item()

и
set_Item()
. И
зменить
имена методов можно, используя атрибут
[
IndexerName
]
.

2

В отличие от языка C#, CLR позволяет создавать статические индексаторы.

33

"кземпляр статического класса не может быть создан или даже объявлен в
программе.
Для таки2 классов запрещено наследование.
Все открытые элеме
нM
ты статического класса доступны только с использованием имени класса.

Упомян
е
м

некоторые полезные статич
еские классы из пространства имѐн
System
. Для преобразования данны2 различны2 типов удобно использовать
класс
Convert
. Класс
Math

содержит большой набор различны2 математически2
функций. Класс
Console

предназначен для чтения и записи информации на ко
нM
соль,

а также для настройки консоли. Класс
Environment

содержит полезные
свойства, описывающие окружение запуска пр
ограммы

(например,
Version



текущая версия платформы .NET).

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

В C# 3.P была представлена концепция
метода расширения

(exten
sion
method). Методы расширения позволяют «добавлять» методы в существующие
типы без создания нового производного типа, перекомпиляции или иного изм
еM
нения ис2одного типа. Методы расширения являются особым видом статич
еM
ского метода, но они вызываются, как е
сли бы они были методами экземпляра в
расширенном типе. Для клиентского кода нет видимого различия между выз
оM
вом метода расширения и вызовом методов, фактически определѐнны2 в типе.

Рассмотрим следующий пример. Пусть разрабатывается программный
проект, в р
азличны2 частя2 которого требуется подсчѐт суммы элементов цел
оM
численного массива. Для реализации данной задачи создан специальный всп
оM
могательный класс, содержащий статический метод подсчѐта:

public

static

class

ArrayHelper

{


public

static

int

Sum(
int
[] array)


{


var

result = 0;


foreach

(
var

item
in

array)


{


result += item;


}



result;


}

}


//
использование

метода

Sum
()

int
[]
m

= {3, 4, 6};

Console
.WriteLine(
ArrayHelper
.Sum(m));

Превратим
Sum()

в метод расширения. Для этого достаточно добавить
ключевое слово
this

перед первым аргументом метода:

public

static

class

ArrayHelper

{


public

static

int

Sum(
this

int
[] array)

34


{ . . . }

}

Теперь метод
Sum()

можно вызывать как традиционным способом, как
«
э
кM
земплярный
»

метод массива:

int
[] m = {3, 4, 6};

Console
.WriteLine(m.Sum());

Подчеркнѐм, что методами расширения могут быть только статические м
еM
тоды
1

статически2 классов. Количество параметров такого метода

произвольно
(один и более), но только первый можно указать с модификатором
this
. Соо
тM
ветственно, метод расширит тип первого параметра.

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

класс
ов
.

12. Конструкторы и инициализация объектов

Конструктор выполняет начальную инициализацию объекта или класса.
Синтаксис описания конструктора напоминает синтаксис описания метода. О
дM
нако имя конструктора всегда совпадает с именем класса, а любое указание на
тип возвращаемого значения отсутствует (даже
void
).

Задача
экземплярны2 конструкторов



создание и инициализация объекта.
Любой экземплярный конструктор в начале свой работы выполн
яет размещ
еM
ние объекта в динамической памяти и инициализацию полей объекта. Различ
аM
ют два вида экземплярны2 конструкторов


конструкторы по умолчанию

и
пользовательские конструкторы
.

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


это всегда конструктор без параметров.

// класс Pet не содержит описания конструктора

public

class


{


public

int

Age;


public

string

Name =
;

}


var

dog

=
new

();


// вызов конструктора по умолчанию

Console
.WriteLine(dog.Age);

//
выводит

"
0
"

Console
.WriteLine(dog.Name);

//
выводит

"
"




1

Язык
C
#
допускает только
методы

расширения, свойств и индексаторов расширения не
существует.

35

Пользовательский конструктор

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

// класс Pet содержит два пользовательских конструктора

public

class

Pe
t

{


public

int

Age;


public

string

Name;



public



{


Name =
;


}



public

int

age,
string

name)


{


Age

=
age
;


Name

=
name
;


}

}

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

public

class


{


public

int

Age;


public

string

Name;



public

int

age

= 0
,
string

name

=
)


{


Age

=
age
;


Name

=
name
;


}

}

Пользовательские конструкторы могут применяться для начальной иниц
иM
ализации
readonly
-
полей (
т.е.
readonly
-
поля доступны для записи, но только в
конструкторе). Пользовательский конструктор может вызвать другой констру
кM
тор того же класса, но только в начале

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

в следу
юM
щем примере:

public

() :
this
(10,
) { . . . }

36

Для вызова экземплярны2 конструкторов используется операция
new
, к
оM
торая возвращает созданный объект. У объекта нельзя вызвать конструктор как
метод (т.е. в виде
имя
-
объекта
.
имя
-
конструктора
).

// вызов конструкторов для создания объекта

var

cat

=
new

();

var

dog =
new

(5,
"Buddy"
);

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

использую
тся для начальной инициализации
статически2 полей класса. Статический конструктор всегда объявляется с м
оM
дификатором
static

и без параметров. Область видимости у статически2 ко
нM
структоров не указывается. В теле статического конструктора возможна работа
тол
ько со статическими полями и методами класса. Статический конструктор
не может вызывать экземплярные конструкторы в начале своей работы.

public

class

Account

{


private

static

double

_tax;



static

Account
()


{


_
tax

= 0.1;


}

}

Статические конструкторы вызываются не программистом, а общеязык
оM
вой средой исполнения в следующи2 случая2
1
:



перед созданием первого объекта класса или при первом обращении к
элементу класса, не унаследованному от предка;



перед первым обращением к стат
ическому полю, не унаследованному от
предка.

При работе с объектами достаточно типичн
ой

является с
итуация, когда

объект
вначале
создаѐтся, а затем настраивается путѐм установки свойств
:

var

s =
new

Student
();

s.Name =
"Mr. Spiderman"
;

s
.
Age

=
18
;

C# позвол
яет совместить создание объекта с его настройкой. Для этого п
оM
сле параметров конструктора в фигурны2 скобка2 требуемы
м

public
-
элемент
ам

класса (обычно это свойства)
пр
исваиваются

и2 значения (если конструктор
не
имел

параметров, можно не указывать круглые скобки после его имени):




1

Если класс содержит ста
тический конструктор (возможно, пустой), компилятор
C
#
ген
е
р
иM
рует код, выполняющий инициализацию статически2 полей класса
непосредственно

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

до

использования класса.

37

var

s =
new

Student

{Name =
"Mr. Spiderman"
, Age =
18
};

Инициализация объектов действует и для классов
-
коллекций. Предполаг
аM
ется, что такой класс реализует интерфейс
IEnumerable

и имеет
publi
c
-
метод
Add()
. Именно этот метод вызывает компилятор, когда обрабатывает код ин
иM
циализации
:

var

x

=
new

List

int
� {
1
,
4
,
8, 8
};

var

y =
new

List

string
� {
"There"
,
"is"
,
"no"
,
"spoon"
};

Инициализация коллекций работает, даже если у метода
Add()

несколько
параметров. В таком случае эти параметры записываются в фигурны2 скобка2:

var

cars

=
new

Dictionary

int
,
string
� {{1,
"
Ford
"
}, {2,
"
Opel
"
}};

13. Наследование классов

Язык C# полностью поддерживает объектно
-
ориентированную концепцию
наследования
для классов. "тобы указать, что один класс является наследником
другого, используется следующий синтаксис:

class

имя
-
класса
-
наследника

:
имя
-
класса
-
предка

{
тело
-
класса
}

Наследование от дву2 и более классов в C#
и
CLR

запрещено. Наследник
обладает всеми полями, методами и свойствами предка, но элементы предка с
модификатором
private

не доступны в наследнике. Конструкторы класса
-
предка не переносятся в класс
-
наследник. При наследовании также нельзя ра
сM
ширить область види
мости класса
, т.е.

internal
-
класс может наследоваться от
public
-
класса, но не наоборот.

Для объектов класса
-
наследника определено неявное преобразование к т
иM
пу класса
-
предка. C# содержит две специальные операции, связанные с контр
оM
лем типов при наследовани
и. Выражение
x
is

T

возвращает значение
true
, е
сM
ли тип объекта
x



это
T

или наследник класса
T
. Выражение
x
as

T

возвращает
объект, приведѐнный к типу
T
, если это возможно, и
null

в противном случае.

Для обращения к методам
класса
-
предка класс
-
наследник м
ожет использ
оM
вать ключевое слово
base

в форме
base
.
метод
-
базового
-
класса
. Если констру
кM
тор наследника должен вызвать конструктор предка, то для этого также испол
ьM
зуется ключевое слово
base
:

конструктор
-
наследника
(
[параметры]
)

:
base
(
[параметры_2]
)

Для
конструкторов производного класса справедливо следующее замеч
аM
ние: в

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

в заголовок конструктора
вызов
base
()
. Если в базовом классе нет конструктора без параметров, прои
сM
2одит ошибка компиляции.

38

Для классов можно указать два модификатора, связанны2 с наследованием.
Модификатор
sealed

определяет

запечатанный
класс
, от которого запрещено
наследование. Модификатор
abstract

определяет

абстрактный класс
, у кот
оM
рого обязательно должны быть наследники. Объект абстрактного класса с
оM
здать нельзя, 2отя статические элементы такого класса можно вызвать:

sealed

class

Sealed
Cla
ss

{ }

abstract

class

AbstractClass

{ }

Класс
-
наследник может дополнять базовый класс новыми элементами, а
может замещать элементы базового класса. Для замещения
нужно

указать в н
оM
вом классе элемент с прежним именем и, возможно, новой сигнатурой:

public

cl
ass


{


public

void

Speak() {
Console
.WriteLine(
); }

}


public

class

Dog

:

{


public

void

Speak() {
Console
.WriteLine(
"I'm a dog"
); }

}

При компиляции данного фрагмента будет получено предупреждающее
сообщение о том, что метод
Dog
.Speak()

закрывает метод базового класса
.Speak()
. "тобы подчеркнуть, что метод класса
-
наследника сознательно з
аM
мещает метод базового класса, используется ключевое слово
new
:

public

class

Dog

:

{


public

new

void

Speak() {
Console
.WriteLine(
"I'm
a dog"
); }

}

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

public

class

A

{


public

void

Do(
int

x) {
Console
.WriteLine(
"A.Do()"
); }

}


public

class

B

:
A

{


public

void

Do(
double

x) {
Console
.WriteLine(
"B.Do()"
); }

}


B

x =
new

B
();

x.Do(3);
//
печатает

"B.D
o
()"

39

Замещение методов класса не является полиморфным по умолчанию. Сл
еM
дующий фрагмент кода печатает две одинаковые строки:


new

()
;


dog

=
new

Dog
();


//
объект

класса

Dog
присвоен

объекту



//
печатает


dog
.
Speak
();

//
также

печатает


Для организации полиморфного вызова применяется
два

модификатор
а
:
virtual

указывается для метода базового класса, который мы 2отим сделать
полиморфным,
override



для методов производны2 классов. "ти методы
должны совпадать по имени, типу и сигнатуре с пер
екрываемым методом кла
сM
са
-
предка.

public

class


{


public

virtual

void

Speak() {
Console
.WriteLine(
); }

}


public

class

Dog

:

{


public

override

void

Speak() {
Console
.WriteLine(
"I'm a dog"
); }

}



new

()
;


dog

=
new

Dog
();


//
печатает


dog
.
Speak
();

//
печатает

"I'm a
dog
"

При описании метода возможно совместное указание модификаторов
new

и
virtual
. Такой приѐм создаѐт нову
ю полиморфную цепочку замещения
:

public

class

A

{


public

virtual

void

Do
() {
Console
.
WriteLine
(
"A.Do()"
); }

}


public

class

B

:
A

{


public

override

void

Do() {
Console
.WriteLine(
"B.Do()"
); }

}


public

class

C

:
A

{


public

new

virtual

void

Do() {
Console
.WriteLine(
"C.Do()"
); }

}


A
[] x = {
new

A
(),
new

B
(),
new

C
()};

x
[0].
Do
();
// печатает "A.Do()"

40

x
[1].
Do
();
// печатает "B.Do()"

x
[2].
Do
();
// печатает "A.Do()"

Если на некоторой стадии построения иерар2ии классов требуется запр
еM
тить дальнейшее переопределение виртуального метода в производны2 класса2,
этот метод помечается ключевым словом
sealed
:

public

class

Dog

:

{


public

override

sealed

void

Speak() { }

}

Для методов
, объявленны2 в
абстрактны2 класс
а2,
можно
применить

м
оM
дификатор
abstract
, который говорит о том, что метод не реализуется в классе,
не содержит тела и

должен обязательно переопределяться в наследнике (в такой
ситуации модификатор
abstract

эквивалентен модификатору
virtual
).

public

abstract

class

AbstractCla
ss

{


//
реализации

метода

в

классе

нет


public

abstract

void


}

Отметим, что наряду с виртуальными методами в классе можно описать
виртуальные свойства, индексаторы и события. Статические элементы класса
не могут быть виртуальными.

14. Класс
System
.
Object

и иерар2ия типов

Диаграмма, показанная на рис.
3
, связывает типы платформы .NET с точки
зрения отношения наследования.

41


Рис.
3
. Иерар2ия типов платформы .NET

Все типы в .NET Framework наследуются (прямо или косвенно) от класса
Syst
em.
Object
1

(
в C# для этого типа используется псевдоним
object
). Тип
System.

является предком все2 типов значений (включая числовые
типы, пользовательские структуры и перечисления). Массивы наследуются от
класса
System.
Array
, а класс
System.
Delegate

является предком все2 делегатов.

Рассмотрим элементы класса
System.
Object

(
в алфавитном порядке
)
.

public

virtual

bool

Equals(
object

obj)

Данный метод определяет, равен ли объект
obj

текущему объекту. Реал
иM
зация
Equals()

по умолчанию обеспечивает р
авенство ссылок для ссылочны2
типов и побитовое равенство для типов значений. Пользовательский тип может
переопределять метод
Equals()
. При этом должны выполняться такие правила:

1.
x.Equals(x) ==
true
.

2.
x
.
Equals
(
y
) ==
y
.
Equals
(
x
)
.

3.
(x.Equals(y) &
y.Equals(z)) ==
true



x.Equals(z) ==
true
.

4.
Вызовы метода
x.Equals(y)

возвращают одинаковое значение до те2
пор, пока объекты
x

и
y

остаются неизменными.

5.
x.Equals(
null
) ==
false
,
если

x !=
null
.

6.
Метод
Equals()

не должен генерировать исключений.




1

Формально, от
object

не наследуются типы
-
указатели, используемые в неуправляемом к
оM
де (например,
int
*
), а также интерфейсы (но интерфейсы приводятся к
object
).

42

Ти
пы, переопределяющие метод
Equals()
, должны также переопределять
метод

(и наоборот); в противном случае коллекции
-
словари м
оM
гут работать неправильно.

Если применяется перегрузка операции равенства
для заданного типа, то этот тип
также
должен переопределять
и
метод
Equals()
.
Реализация
Equals()

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

public

static

bool

Equals(
object

a,
object

b)

Метод определяет, равны
ли
экземпляры
a

и
b
.
Если оба аргумента равны
null
,
метод возвращает
true
.
Если только один аргумент равен
null
,
возвращ
аM
ется
false
.
Если оба аргумента не равны
null
, возвращается
a.
Equals(
b
)
.

protected

virtual

void

Finalize
()

Метод
Finalize()

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

public

virtual

int

()

Метод

играет роль 2
е
ш
-
функции для определѐнного типа.
"тот метод можно использовать в алгоритма2 2еширования и таки2
структура2
данны2, как 2
е
ш
-
таблицы.

Реализация метода

по умолчанию не
гарантирует уникальность возвращаемы2 кодов. Пользовательские типы могут
переопределять данный метод для эффективного вычисления 2
е
ш
-
функции.
Если два объекта при сравнении

оказались равны, методы

эти2
объектов должны возвращать одинаковые значения. Однако если при сравн
еM
нии оказалось, что объекты не равны, методы

не обязательно
должны возвращать разные значения.

public

Type

()

Данный метод

возвращает объект
System.
Type

для текущего экземпляра.
Объект
System.
Type

содержит

метаданные, связанные с классом текущего э
кM
земпляра.

protected

object

MemberwiseClone
()

Метод
MemberwiseClone()

применяется для создания неполной копии об
ъM
екта. Метод
создаѐт новый объект

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

С
ледовательно, ссылки в ис2одном объекте и его клоне указывают
на один и тот же объект.

43

public

static

bool

ReferenceEquals(
object

a,
object

b)

"тот статический метод возвращает значение
true
, если параметр
a

соо
тM
ветствует тому же экземпляру, что и параметр
b
, или же оба они равны
null
; в
противном случае метод возвращает
false
.

public

virtual

string

ToString
()

Метод
ToString()

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

Так как
System.
Object

является предком любого типа, переменной типа
object

можно присвоить любую переменную. Если для ссылочны2 типов при
этом проис2одит только присваивание указателей,
для типов значений выпо
лM
няется специальная операция, называемая
операцией упаковки

(boxing)
1

. При
упаковке в динамической памяти создаѐтся объект, содержащий значение п
еM
ременной и информацию о еѐ типе. Упакованный объект мож
но

подвергнут
ь

обратному преобр
азованию


операции распаковки

(unboxing).

int

i

= 123;

object

o

=
i
;
//
операция

упаковк
и

int

j

= (
int
)
o
;
//
операция
распаковк
и

По форме
операция
распаковка выглядит как приведение типов
, однако т
аM
ковой не является
.

Следующий код при выполнении генерирует исключение:

object

o

= 123;
//
операция

упаковк
и литерала
int

short

j = (
short
)o;
//
генерируется

InvalidCastException

При распаковке необ2одимо указывать точный тип упакованного объекта:

short

j

= (
short
)(
int
)
o
;
//
распаковка, затем приведение типов

15. Структуры

Структура



это пользовательский тип значения, поддерживающий всю
функциональность класса, кроме наследования. Пользовательская структура в
простейшем случае позволяет инкапсулировать нескольк
о полей различны2 т
иM
пов. Но элементами структуры могут быть не только поля, а и методы, сво
йM
ства, события, константы. Структуры также могут реализовывать интерфейсы.

Синтаксис определения структуры следующий:

модификаторы

struct

имя
-
структуры

{


элемент
ы
-
структуры

}




1

Операция упаковки выполняется и в случае, когда переменной типа
интерфейс присваив
аM
ется переменная типа значения. "тот аспект будет разобран при рассмотрении интерфейсов.

44

При описании
экземплярны2
полей структуры следует учитывать, что они
не могут быть инициализированы при объявлении

(для статически2 полей ин
иM
циализация при объявлении возможна)
. Как и класс, структура может соде
рM
жать конструкторы.
В

структуре можно объявить

статический конструктор

или

экземплярный конструктор с параметрами, причѐм в теле конструктора необ2
оM
димо инициализировать все поля структуры. Ещѐ одно отличие структуры от
класса


в структуре указатель на экземпляр
this

доступен

не только для чт
еM
ния, но и для записи.

Рассмотрим пример структуры для представления точки в пространстве:

public

struct

Point3D

{


public

readonly

double

X, Y, Z;



public

Point3D(
double

x,
double

y
,
double

z = 0.0
)


{


X = x;


Y = y;


Z =
z
;


}



public

Point3D(
Point3D

point)


{


this

=
point
;


}

}

Если в типе объявляется поле
-
структура, все элементы структуры получат
значения по умолчанию. ёналогичная ситуация будет при объявлении локал
ьM
ной пер
еменной
-
структуры и вызове конструктора структуры без параметров
1
.
Без вызова конструктора поля переменной
-
структуры не инициализированы.

// поля pо не инициализированы, их надо установить до использования

Point
3
D

p
1;


// поля pп инициализированы
значениями л.л

Point
3
D

p
2 =
new

Point
3
D
();


// поля pр инициализированы значениями п.л, р.л, л.л

Point
3
D

p
3 =
new

Point
3
D
(2.0, 3.0);

Локальные
переменные структур
ного типа

размещаются в стеке прилож
еM
ния. Структурные переменные можно присваивать друг другу,

при этом выпо
лM
няется копирование данны2 структуры на уровне полей. Все структуры насл
еM
дуются от класса
System.
.

К
ласс


переопределяет некоторые



1

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

45

методы класса
Object
. В частности, переопределяется метод
Equals()

для сра
вM
нения объектов пут
ѐм сравнения и2 полей.

16. Перечисления

Перечисление



это тип, содержащий в качестве элементов именованные
целочисленные константы. Рассмотрим синтаксис определения перечисления:

модификаторы

enum

имя
-
перечисления

[
:

тип
-
элемента
]

{


элемент
-
перечисления
-
1

[
=

значение
-
элемента]
,


. . .


элемент
-
перечисления
-
N

[
=

значение
-
элемента]

}

Перечисление может предваряться модификатором доступа. Если
указан
тип
-
элемента
, то он определяет тип каждого элемента перечисления.
Допуст
иM
мы т
ип
ы
byte
,
sbyte
,
short
,
ushort
,
int
,
uint
,
long
,
ulong

(причѐм нужно и
сM
пользовать именно псевдоним типа
C
#).

По умолчанию применяется тип
int
.
Для элементов перечисления область видимости указать нельзя. Значением
элемента перечисления должна быть целочисленная конс
танта. Если значение
не указано, элемент будет на единицу большее предыдущего элемента (первый
элемент принимает значение
0
). Заданные значения элементов перечисления
могут повторяться.

Приведѐм примеры перечислений:

public

enum

Season

{
Winter
,
Spring
,
Su
mmer
,
Autumn

}


public

enum

ErrorCode

:
byte

{


First = 1,


Fourth

= 4

}

После описания перечисления можно объявить переменную соответств
уM
ющего типа:

Season

s

=
Season
.
Spring
;

Console
.
WriteLine
(
s
);


//
выводит

на

печать

Spring

Переменные перечисления поддерживают следующие операции:
==
,
!=
,

,

,
=
,
�=
, бинарны
е

+

и



(с ограничением на тип операндов и результата),

^
,
&
,
|
,
~
,
++
,
--
.
При помощи явного преобразования типов переменной перечисления
можно присвоить значение,
которое в перечислении не описано:

Season

p =
Season
.Winter + 3;

int

x =
Season
.Autumn
-

Season
.Summer;

Season

r =
Season
.Autumn
-

2;

Season

s

= (
Season
)30;

46

Класс

System
.
Enum

является

базов
ым

для

все2

перечислений
.
Табл.
4

с
оM
держит описание некоторы2 методов
класса

System.
Enum
.

Таблица
4

Некоторые методы
System.
Enum

Имя метода

Категория

Описание


статический

Возвращает строку с именем элемента для ук
аM
занного типа и значения перечисления


статический

Возвращает массив строк с именами элементов
для указанного типа перечисления


статический

Возвращает тип перечисления


статический

Возвращает массив значений элементов для
указанного типа перечисления

HasFlag()

экземплярный

Возвращает
true
, если перечисление содержит
заданные флаги (т.е. набор значений)

IsDefined()

статический

Возвращает
true
, если указанный элемент с
оM
держится в заданном типе перечисления

Parse()

статический

Конвертирует строку с именем элемента в п
еM
ременн
ую перечисления

TryParseEn
um
�()

статический

Делает попытку конвертирования строки в п
еM
ременную перечисления

17. Интерфейсы

В языке C# запрещено множественное наследование классов. Тем не м
еM
нее, существует концепция, позволяющая имитировать множественное

насл
еM
дование. "та концепция интерфейсов.
Интерфейс

представляет собой
набор
объявлений

свойств, индексаторов, методов и событий. Класс или структура
могут
реализовывать

интерфейс. В этом случае они берут на себя обязанность
предоставить полную реализацию элементов интерфейса (2отя бы пустыми м
еM
тодами).

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

и (или)
. При объявлении элеме
нM
тов интерфейса не могут использоваться следующие модификаторы:
abstract
,
public
,
protected
,
internal
,
private
,
virtual
,
override
,
static
. Считается, что
все элеме
нты интерфейса имеют
public
-
уровень доступа:

public

interface

IFlyable

{


void

Fly();
//
метод


double

Speed {
;
; }
//
свойство

}

"тобы указать, что тип реализует некий интерфейс, используется синта
кM
сис
имя
-
типа

:
имя
-
интерфейса

при записи заголовка типа. Если класс является
производным от некоторого базового класса, то имя базового класса указывае
тM
ся перед именем реализуемого интерфейса.

47

"лементы интерфейса допускают явную и неявную реализацию. При
нея
вM
ной реализации

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

элемент типа называется по форме
имя
-
интерфейса
.
имя
-
элемента
, а
указание любы2 модификаторов для элемента при этом запрещ
ается.

public

class

Falcon

:
IFlyable

{


// неявная реализация интерфейса )Flyable


public

void

Fly() {
Console
.WriteLine(
"Falcon flies"
); }


public

double

Speed {
;
; }

}


public

class

Eagle

:
IFlyable

{


//
обычный

метод


public

void

PrintType() {
Console
.WriteLine(
"Eagle"
); }



//
явная

реализация

интерфейса

IFlyable


void

IFlyable
.Fly() {
Console
.WriteLine(
"Eagle flies"
); }


double

IFlyable
.Speed {
;
; }

}

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

Eagle

eagle

=
new

Eagle
();
// обычное создание объекта

eagle
.
PrintType
();
// у объекта доступен только этот метод

IFlyable

x

=
eagle
;
// переменная интерфейса

x
.
Fly
();
// получили доступ к
элементам интерфейса

x
.
Speed

= 60;

Все неявно реализуемые элементы интерфейса по умолчанию помечаются
в классе как
sealed
. ё значит, наследование классов не ведѐт к прямому насл
еM
дованию реализаций:

public

interface

ISimple

{


void

M();

}


public

class

Base

:
ISimple

{


public

void

M() {
Console
.Write(
"Base.M()"
); }

}


48

public

class

Descendant

:
Base

{


public

void

M() {
Console
.Write(
"Descendant.M()"
); }

}


Base

x =
new

Base
();

Descendant

y =
new

Descendant
();

ISimple

xi = x, yi = y;


x
.
M
(
);
// печатает "Base.M()"

y.M();
//
печатает

"Descendant.M()"

xi
.
M
();
// печатает "Base.M()"

yi
.
M
();
// печатает "Base.M()"

"тобы осуществить наследование реализаций, требуется при
неявной
ре
аM
лизации использовать м
одификаторы
virtual

и
override

(
п
ри явной реализ
аM
ции использование
эти2
модификаторов невозможно)
:

public

class

Base

:
ISimple

{


public

virtual

void

M() {
Console
.Write(
"Base.M()"
); }

}


public

class

Descendant

:
Base

{


public

override

void

M() {
Console
.Write(
"Descendant.M()"
); }

}

Подобно классам, интерфейсы могут наследоваться от други2 интерфе
йM
сов. При этом наследование интерфейсов может быть множественным. Один
класс может реализовывать несколько интерфейсов


имена интерфейсов пер
еM
числяются п
осле имени класса через запятую.

18. Универсальные шаблоны

Универсальные шаблоны

(generics) позволяют при разработке пользов
аM
тельского типа или метода указать в качестве параметра тип, который конкр
еM
тизируется при использовании. Универсальные шаблоны приме
нимы к классам,
структурам, интерфейсам, делегатам и методам.

18.1. Универсальные классы и структуры

Поясним необ2одимость универсальны2 шаблонов на следующем прим
еM
ре. Пусть разрабатывается класс для представления структуры данны2 «стек».
"тобы не создават
ь отдельные версии стека для 2ранения данны2 определѐ
нM
ны2 типов, программист выбирает базовый тип
object

как тип элемента:

public

class

Stack

{


private

object
[] _items;

49


public

void

Push(
object

item) { . . . }


public

object

Pop
() { . . . }

}

Класс
Stack

можно использовать для разны2 типов данны2:

var

stack =
new

Stack
();

stack.Push(
new

Customer
());

Customer

c = (
Customer
)stack.Pop();


var

stack2 =
new

Stack
();

stack2.Push(3);

int

i = (
int
)stack2.Pop();

Однако универсальность класса
Stack

имеет и отрицательные моменты.
При извлечении данны2 из стека необ2одимо выполнять приведение типов. Для
типов значений (например,
int
) при помещении данны2 в стек и при извлеч
еM
нии выполняются операции упаковки и распаковки, что отрицательно сказыв
аM
ется н
а производительности. И, наконец, неверный тип помещаемого в стек
элемента может быть выявлен только на этапе выполнения, но не компиляции.

var

stack

=
new

Stack
();
// планируем сделать стек чисел

stack
.
Push
(1);

stack
.
Push
(2);

stack
.
Push
(
"
three
"
);
// вставили не число, а строку

var

sum = 0;

for

(
var

i = 0; i 3; i++)

{


//
код
компилируется, но
при выполнении
на третьей итерации


// будет сгенерирована исключительная ситуация


sum

+= (
int
)
stack
.
Pop
();

}

Необ2одимость устранени
я

о
писанны2 недостатков явилась основной пр
иM
чиной появления универсальны2 шаблонов, представленны2 в C# 2.P.

Опишем класс
Stack

как универсальный тип. Для этого используется сл
еM
дующий синтаксис: после имени класса в угловы2 скобка2 указывается
пар
аM
метр типа
.
"тот параметр может затем использоваться при описании элеме
нM
тов класса (в нашем примере


методов и массива)

на месте указания на тип
.

public

class

Stack
&#xT000;T

{


private

T[] _items;


public

void

Push(T item) { . . . }


public

T

Pop
() { . . . }

}

Использовать универсальный тип «как есть» в клиентском коде нельзя, так
как он является не типом, а, скорее, «чертежом» типа. Для работы со
Stack
T&#xT100;

50

необ2одимо использовать
сконструированный тип

(
constructed

type
), указав в
угловы2 скобка2 аргумент типа. ё
ргумент
-
тип может быть любым типом.
Можно создать любое количество экземпляров сконструированны2 типов, и
каждый из ни2 может использовать разные аргументы типа.

Stack

int
� stack = new
Stack

int
�();

stack.Push(3);

int

x = stack.Pop();

Обратите внимание: пр
и работе с типом
Stack

int


отпала необ2одимость
в выполнении приведения типов при извлечении элементов из стека. Кроме
этого, теперь компилятор отслеживает, чтобы в стек помещались только да
нM
ные типа
int
. И ещѐ одна особенность: нет необ2одимости в упаков
ке и расп
аM
ковке типа значения, а это приводит к росту производительности.

Подчеркнѐм некоторые особенности сконструированны2 типов. Во
-
первы2, сконструированный тип не связан отношением наследования с униве
рM
сальным типом. Во
-
вторы2, даже если классы
A

и
B

связаны наследованием,
сконструированные типы на и2 основе этой связи лишены. В
-
третьи2, статич
еM
ские поля, описанные в универсальном типе, уникальны для каждого сконстр
уM
ированного типа.

При объявлении универсального шаблона можно использовать несколько
па
раметров
-
типов. Приведѐм фрагмент описания класса для 2ранения пар
«ключ
-
значение» с возможностью доступа к значению по ключу
1
:

public

class

Dictionary
&#xK, V;K, V

{


public

void

Add(K key, V value) { . . . }


public

V
this
[K key] { . . . }

}

Сконструированный тип для
Dictionary
K, &#xK, -;ȹV;V

должен быть основан на
дву2 аргумента2
-
типа2:

Dictionary

int
,
Customer
� dict =
new

Dictionary

int
,
Customer
�();

В языке C# существует операция
default
, которая возвращает значение по
умолчанию для переменной указа
нного типа. "та операция может использ
оM
ваться в те2 метода2, где возвращаемое значение задано как параметр типа:

public

class

Cache
&#xK, V;K, V

{


// метод для поиска элемента по ключу


// если элемент найден, то метод возвращает его


// иначе метод
возвращает значение по умолчанию для 6




1

И класс
Stack
T&#x-3T7;
, и

класс
Dictionary
K, VK-3;&#x,-3 ;&#x-134;&#xV-30;

рассмотрены только как примеры. В .NET
Framework

уже имеются полноценные аналоги данны2 классов.

51


public

V LookupItem(K key)


{



default
(V);


}

}

18.2. Ограничения на параметры
универсальны2 типов

Как правило, универсальные типы не просто 2ранят данные, но

и
вызывают методы у объекта, чей тип указан как параметр. Например, в классе
Dictionary
K, &#xK, -;ΉV;V

метод
Add()

может использовать метод

для
сравнения ключей:

public

class

Dictionary
&#xK, V;K, V

{


public

void

Add(K key, V value)


{


. . .



if


//
ошибка

компиляции
!


. . .


}

}

Ошибка компиляции в этом примере возникает по следующей причине.
Так как тип
K

может быть любым, то у
параметра

key

можно вызывать только
методы
, определѐнные в

object
. Проблему можно решить, используя привед
еM
ние типов:

public

class

Dictionary

K
,
V


{


public

void

Add(K key, V value)


{


. . .


if

(((
IComparable
)



. . .


}

}

Недостаток такого под2ода


много
численность операций приведения. К
тому же, если у сконструированного типа параметр
K

не поддерживает инте
рM
фейс
IComparable
, то при работе программы будет сгенерировано исключение
InvalidCastException
.

C# допускает указание
ограничени
й

(
constraint
s
) для
каждого параметра
универсального типа. Только тип, удовлетворяющий ограничениям, может
быть применѐн для записи сконструированного типа.

Ограничения делятся на первичные ограничения, вторичные ограничения
и ограничения конструктора.
Первичное ограничение



это

тип, который не я
вM
ляется
sealed
,
за исключением
System
.
Object
,
System
.
Array
,

System
.
Delegate
,
System
.
MulticastDelegate
,
System
.
,
System
.
Enum

или
System
.
Void
.
52

Первичное ограничение
требует, чтобы
аргумент
сконструированного

типа

приводился к у
казанному типу.

Существуют два особы2 первичны2 ограничения


class

и
struct
.
Огр
аM
ничению
class

удовлетворяет любой ссылочный тип


класс, интерфейс, дел
еM
гат. Ограничению
struct

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

тип
ов

с поддержкой
null
.

Вторичное ограничение



это интерфейс.
Вторичное ограничение

требует,
чтобы аргумент
сконструированного типа

реализовывал указанный интерфейс.

Ограничение конструктора

имеет вид
new
()

и
треб
ует
, чтобы
аргумент
сконструированного типа
имел

конструктор без п
араметров.

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

универсального типа
, двоеточие и
список огр
аM
ничений
:



ноль или одно первичное ограничение;



ноль или несколько вторичны2 ограничений;



ноль ил
и одно ограничение конструктора (если не задано первичное
ограничение
struct
)
.

В следующем примере
демонстрируется
использ
ование

ограничений на
различные параметры универсального типа:

public

class

EntityTable
&#xK, E;K, E



where

K :
IComparable
&#xK000;K,
IPersistable



where

E :
Entity
,
new
()

{


public

void

Add(K key, E entity)


{


. . .


if

(
key
.
(
x
) 0) { . . . }


. . .


}

}

18.3. Ковариантность и контравариантность

Определим понятия
ковариантности и контравариантности для сконстру
иM
рованны2 типов данны2. Для этого введѐм отношение частичного порядка на
множестве ссылочны2 типов:










ː ˔ˎ ˖ ˕˔

(
˒˓ ˏˑ

ˎ

ˍˑ˔ ːːˑ
)
ˑ˕



.

Если имеется тип
C
T0;T
, а также типы
T
1

и
T
2

(
T
1


T
2
), то
C
T0;T

назовѐм:



ковариантным
, если
C

T
1
> ≤
C

T
2

;



контравариантным
, если
C

T
2
> ≤
C

T
1

;



инвариантным
, если не верно ни первое, ни второе утверждение.

Понятия частичного порядка типов, ковариантности и контравариантности
связаны с приведением типов. Тот

факт, что тип
T
1

«меньше» типа
T
2
, означает
возможность неявного приведения переменной типа
T
1

к типу
T
2
. Как указыв
аM
53

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

Универсальные классы и структ
уры инвариант
н
ы, однако универсальные
интерфейсы могут быть описаны как ковариантные или контравариантные о
тM
носительно некоего параметра
-
типа. "тобы указать на ковариантность относ
иM
тельно параметра
T
, следует использовать ключевое слово
out

при описании
параметра типа. На контравариантность указывает ключевое слово
in

при оп
иM
сании параметра типа.

public

interface

IOutOnly

out

�T

{


T
this
[
int

index] {
; }

}


public

interface

IInOnly

in

�T

{


void

Process
(
T

x
);

}

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

18.4. Универсальные методы

В некоторы2 случая2 достаточно параметриз
иро
вать не весь пользовател
ьM
ский тип, а только отдельный метод.
Универсальные методы

объявляются с использованием параметров
-
т
ипов в угловы2 скобка2 после
имени метода
1
. Как и при описании универсальны2 типов, универсальные м
еM
тоды могут содержать ограничения на параметр
-
тип.

void

&#xT000;PushMultipleT(
Stack
&#xT000;T stack,
params

T[] values)

{


foreach

(T value
in

values)


{


stack
.
Push
(
value
);


}

}

Использование универсального метода
&#xT000;PushMultipleT

позволяет раб
оM
тать с любым сконструированным типом на основе
Stack
T&#xT100;
.

Stack

int
� stack =
new

Stack

int
�();

PushMultiple

int
�(
stack
, 1, 2, 3, 4);




1

Универсальные методы могут заменить перекрытие методов в пользовательском типе, е
с
ли
алгоритмы работы различны2 версий перекрыты2 мет
одов не зависят от типов параме
т
ров.

54

В большинстве ситуаций компилят
ор способен самостоятельно сконстру
иM
ровать тип универсального метода на основе анализа типов фактически2 пар
аM
метров. "то позволяет записывать вызов метода без указания типа:

var

stack

=
new

Stack

int
�();

PushMultiple
(
stack
, 1, 2, 3, 4);

Рассмотрим
следующий пример:

public

class

C

{


public

static

void

M(
string

a,
string

b) { . . . }


public

static

void

&#xT000;MT(T a, T b) { . . . }

}


int

a = 10;

byte

b = 20;

string

s =
"ABC"
;

C
.M(a, b);
&#xint0;// C.Mint(a,b)

C
.M(s, s);
//
C.M(s,s)

C
.M
string
�(s, s);
&#xstri;&#xng00;// C.Mstring(s,s)

В первом случае компилятор сконструирует метод
&#xT000;MT()

как
M
int
�()
,
потому что к типу
int

приводится и переменная
a
, и переменная
b
. Второй в
ыM
зов показывает, что при наличии альтернатив компилятор
всегда выбирает ве
рM
сию метода без универсального параметра, если только метод не сконструир
оM
ван явно.

19. Использование универсальны2 шаблонов

В данном параграфе приведено несколько примеров использования ун
иM
версальны2 шаблонов в элемента2 платформы .NET.

19.1. Кортежи

В языка2 программирования
кортежем

(tuple) называется структура да
нM
ны2, содержащая фиксированный набор разнотипны2 значений. В платформе
.NET для создания кортежей доступен набор универсальны2 классов вида
System.
Tuple

. Всего имеется восемь

универсальны2 классов
Tuple

, которые
просто различаются количеством параметров типов.

Tuple

string
,
int
� t =
new

Tuple

string
,
int
�(
"Hello"
, 4);

Console
.WriteLine(
"{0}
-

{1}"
, t.Item1, t.Item2);

Статический класс
System.
Tuple

содержит восемь перегруженн
ы2 версий
метода
Create()

для конструирования кортежа с заданным числом элементов:

Tuple

string
,
int
� t =
Tuple
.Create(
"Hello"
, 4);

55

19.2. Типы, допускающие значение null

Примером универсальны2 типов являются
типы, допускающие значение
null

(nullable types)
.
"ти

типы являются экземплярами структуры
Sy
s-
tem.
Nullable
&#xT000;T
, где
T

должен быть типом значения. Структура
Nullable
T&#xT100;

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

при
HasValue
,
ра
вM
ном
false
,
вед
ѐ
т к генерации исключения. Также в
Nullable
&#xT000;T

определѐн м
еM
тод
.

Язык C# предлагает компактную форму объявления типа, допускающего
значение
null
. Для этого после имени типа указывается
знак вопроса
.

int
?
x

= 12
3;

int
? y =
null
;

if

(x.HasValue)
Console
.WriteLine(x.Value);

if

(y.HasValue)
Console
.WriteLine(y.Value);

int


int


Для типов значений определено неявное преобразование к соответству
юM
щему типу, допускаю
щему значение
null
. Если для типа
S

возможно привед
еM
ние к типу
T
, то такая возможность имеется и для типов
S?

и
T?
. Также возмо
жM
но неявное приведение типа
S

к типу
T?

и явное приведение
S?

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


если значение типа
S?

не определено.

int

x

= 10;

int
?
z

=
x
;
// неявное приведение int к int?

double
?
w

=
z
;
// неявное приведение int? к double?

double

y = (
double
)z;
//
явное

приведение

int?
к

double

Хотя для структуры
Nullable
&#xT000;T

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



ёрифметические операции возвращают значение
null
, если 2отя

бы один
из операндов равен
null
.



Операции сравнения, кроме
==

и
!=
, возвращают значение
false
, если 2
оM
тя бы один из операндов равен
null
.



Операции равенства
==

и
!=

считают две переменные, равные
null
, ра
вM
ными между собой.



Если в операция2
&

и
|

участвуют операнды типа
bool
?
, то
null

|
true

равняется
true
, а
null

&
false

равняется
false
.

int
? x = 3, y = 5, z =
null
;

Console
.WriteLine(x + y);
// 8

Console
.WriteLine(x + z);
// null

Console
.WriteLine(x y);
// true

Console
.Writ
eLine(x z);
// false

56

Console
.
WriteLine
(
null

==
z
);
// true

С типами, допускающими значение
null
, связана операция
??
. Результатом
выражения
a ?? b

является
a
, если
a

содержит некое значение, и
b



в проти
вM
ном случае. Таким образом,
b



это знач
ение, которое следует использовать, е
сM
ли
a

не определено. Тип выражения
a ?? b

определяется типом операнда
b
.

int
?
x

=
();

int

int
? z = x ?? y;

int

i

=
z

??
-
1;

Операцию
??

можно применить и для ссылочны2 типов:

string


Console
.WriteLine(s ??
"Unspecified"
);

В этом фрагменте кода на консоль выводится значение строки
s
, или
"Unspecified"
, если
s ==
null
.

19.3. Прочие примеры универсальны2 шаблонов

Структура
System.
ArraySegment
&#xT000;T

является «обѐрткой»
над массивом,
определяющей интервал элементов массива. Для одного массива можно создать
несколько объектов
ArraySegment
&#xT000;T
, которые могут задавать даже перекрыв
аM
ющиеся интервалы. Работать с «обѐрнутым» массивом можно, используя сво
йM
ство
ArraySegment
T&#xT100;.Arr
ay
.

int
[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

var

firstSeg =
new

ArraySegment

int
�(a, 2, 6);
//
первый

сегмент

var

secondSeg =
new

ArraySegment

int
�(a, 4, 3);
//
второй

сегмент

firstSeg
.
Array
[3] = 10;
// изменяем четвёртый элемент массива a

Класс
System.
Lazy
T0;T

служит для поддержки отложенной инициализации
объектов. Данный класс содержит булево свойство для чтения
IsValueCreated

и
свойство для чтения
Value

типа
T
. Использование
Lazy
&#xT000;T

позволяет задержать
создание объекта до первого обращения

к свойству
Value
. Для создания объекта
используется либо конструктор без параметров типа
T
, либо функция, перед
аM
ваемая конструктору
Lazy
&#xT000;T
.

Lazy

Student
� lazy =
new

Lazy

Student
�();

Console
.WriteLine(lazy.IsValueCreated);
// false

Student

s =
lazy.Value;

Console
.WriteLine(lazy.IsValueCreated);
// true

20.
Делегаты

Делегат



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

57

public

delegate

double

Function
(
double

x);

public

delegate

void

Subroutine
(
int

i);

Делегат


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


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

После объявления делегата можно объявить переменные этого типа:

Function

F
;

Subroutine

S
;

Переменные делегата иниц
иализируются конкретными методами при и
сM
пользовании
конструктора делегата

с одним параметром


именем метода
(или именем другого делегата). Если делегат инициализируется статическим
методом, требуется указать имя класса и имя метода, для инициализации э
кM
зе
мплярным методом указывается объект и имя метода. При этом метод должен
обладать под2одящей сигнатурой:

F

=
new

Function
(
ClassName
.
SomeStaticFunction
);

S =
new

Subroutine

Для инициализации делегата можно использовать упрощѐнный
синта
кM
сис



достаточно указать имя метода без применения
new
()
.

F =
ClassName
.SomeStaticFunction;


После того как делегат инициализирован, инкапсулированный в нем метод
вызывается с указанием аргументов метода непосредственно пос
ле имени п
еM
ременной
-
делегата
.

Приведѐм пример работы с делегатами. Создадим класс, содержащий м
еM
тод расширения для трансформации массива целы2 чисел:

public

static

class

ArrayHelper

{


public

static

int
[] Transform(
this

int
[] data,
Transformer

f)


{


var

result =
new

int
[data.Length];


for

(
int

i = 0; i data.Length; i++)


{


result[i] = f(data[i]);


}



result
;


}

}

58

Тип
Transformer

является делегатом, который определяет способ преобр
аM
зования
отдельного числа. Он

описан

следующим

образом
:

public

delegate

int

Transformer
(
int

x);

Создадим класс, который использует
ArrayHelper

и
Transformer
:

public

class

MainClass

{


public

static

int

TimesTwo(
int

i) {

i * 2; }



public

int

AddFive(
int

i) {

i + 5; }



public

static

void

Main()


{


int
[] a = {1, 2, 3};


Transformer

t = TimesTwo;


a = a.Transform(t);



var

c =
new

MainClass
();


a = a.Transform(c.AddFive);


}

}

Необ2одимо заметить, что
делегаты обладают
контравариантностью

о
тM
носительно типов параметров и
ковариантностью

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

// два класса, связанных наследованием

public

class

Person

{ . . . }

public

class

Student

:
Person

{ . . . }


//
делегат

для

обработки

public

delegate

Person

Register
(
Person

p);


// вспомогательный класс с методом обработки

public

class

C

{


public

static

Student

M(
object

o) {

new

Student
(); }

}


// присваивание возможно благодаря ко
-

и контравариантности

Register

f

=
C
.
M
;

Делегаты могут быть объявлены как универсальные типы. При этом доп
уM
стимо использование ограничений на

параметр типа, а также указания на ков
аM
риантность и контравариантность параметров типа.

59

public

delegate

TResult
Transformer

in

T,
out

�TResult(T x);


public

static

class

ArrayHelper

{


public

static

&#xT, T;&#xResu;&#xlt00;TResult[] TransformT, TResult(
this

T[] data,


Transformer
&#xT, T;&#xResu;&#xlt00;T, TResult f)


{


var

result =
new

TResult[data.Length];


for

(
int

i = 0; i data.Length; i++)


{


result[i] = f(data[i]);


}



result
;


}

}

Ключевой особенностью делегатов является то, что они могут инкапсул
иM
ровать не один метод, а несколько. Подобные объекты называются
групповыми
делегатами
. При вызове группового делегата срабатывает вся цепочка инка
пM
сулированны2 в нем методов.

Групповой деле
гат объявляется таким же образом, как и обычный. Затем
создаѐтся несколько объектов делегата, все они связываются с некоторыми м
еM
тодами. После этого использу
е
тся операция
+

для объединения делегатов в
один групповой делегат. Если требуется удалить метод из

цепочки группового
делегата, использу
е
тся операция

. Если из цепочки удаляют последний метод,
результатом будет значение
null
.

Приведѐм пример использования группового делегата типа
Transformer
:

int
[] a = {1, 2, 3};

var

c =
new

MainClass
();

Transformer

int
,
int
� x = TimesTwo, y = c.AddFive, z;

z

=
x

+
y
;
// z



групповой делегат, содержит две

функции

a
.
Transform
(
z
);

Любой пользовательский делегат можно рассматривать как наследник
класса
System.
MulticastDelegate
, который, в свою очередь, наследует
ся от
класса
System.
Delegate
. Рассмотрим элементы
System.
Delegate
:



Перегруженный статический метод
CreateDelegate()

позволяет созд
аM
вать делегаты на основе информации о типе и методе.



Метод
Clone()

создаѐт копию объекта
-
делегата.



Статический метод
Com
bine()

объединяет в групповой делегат два объе
кM
та
-
делегата или массив таки2 объектов.



Статические методы
Remove()

и
RemoveAll()

удаляют указанный объект
-
делегат из группового делегата
1
.




1

Делегаты относятся к неизменяемым типам. Поэтому методы
Combine()

и
Remove()

во
зM
вращают новые объекты
-
делегаты.

60



Метод

возвращает массив инкапсулированны2 об
ъM
е
ктов
-
делегатов.



Свойство

позволяет получить информацию о методе, инкапсул
иM
рованном объектом
-
делегатом.



Свойство

содержит ссылку на объект, связанн
ый

с инкапсулир
оM
ванным методом (
для экземплярны2 методов или методов расширения
).

В пространстве
имѐн

System

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


Func


и
Action

, делегаты
для функций конвертирования
Converter

in

TInp
ut,
out

�TOutput
, сравнения
Comparison

in

�T

и предиката
Predicate

in

�T
.

21. ёнонимные методы и лямбда
-
выражения

Назначение
анонимны2 методов

(anonymous methods) заключается в том,
чтобы сократить объѐм кода, который должен писать разработчик при испол
ьM
зо
вании делегатов. Если рассмотреть примеры предыдущего параграфа, то оч
еM
видно, что даже для объектов
-
делегатов, содержащи2 минимум действий, нео
бM
2одимо создавать метод (а, возможно, и отдельный класс) и инкапсулировать
этот метод в делегат
е
. При применении
анонимны2 методов формируется
безымянный блок кода, который назначается объекту
-
делегату.

Синтаксис объявления анонимного метода включает ключевое слово
delegate

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

Модифицируем фрагмент кода из предыдущего параграфа, исп
ольз
уя

ан
оM
нимные методы:

int
[] a = {1, 2, 3};

Transformer

int
,
int
� t =
delegate
(
int

x) {

x * x; };

a
.
Transform
(
t
);

Лямбда
-
выражения

и
лямбда
-
операторы



это альтернативный синтаксис
записи анонимны2 методов. Начнѐм с рассмотрения лямбда
-
операторов.

Пусть
имеется анонимный метод:

Func

int
,
bool

f

=
delegate
(
int

x
)


{


int

y = x
-

100;



�y 0;


};

При использовании лямбда
-
операторов

список параметров отделяется от
тела оператора символами
�=
, а ключевое слово
delegate

не указывается:

61

Func

int
,
bool
� f = (
int

�x) = {
int

y = x
-

100;

�y 0; };

Более того, так как мы уже фактически указали тип аргумента лямбда
-
оператора слева пр
и объявлении f, то его можно не указывать справа. В случае
если у нас один аргумент, можно опустить обрамляющие его скобки
1
:

Func

int
,
bool
�� f = x = {
int

y = x
-

100;

�y 0; };

Когда лямбда
-
оператор содержит в своѐм теле единственный оператор
, он может быть записан в компактной форме
лямбда
-
выражения
:

Func

int
,
bool
��� f_2 = x = x 0;

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


var

использовать нельзя.

Рассмотрим
пример, демонстрирующий применение лямбда
-
выражений.
Используем метод
Transform()

из предыдущего параграфа, чтобы получить из
числового массива массив булевы2 значений, а из него


массив строк. Обрат
иM
те внимание на компактность получаемого кода.

int
[]
a

=

{1, 2, 3};

string
[]
s

=
a
.
Transform
(
x

�=
x

� 2).
Transform
(
y

�=
y
.
ToString
());

// s
содержит

"False", "False", "True"

ёнонимные методы и лямбда
-
операторы способны за2ватывать внешний
контекст вычисления. Если при описании тела анонимного метода применялась

внешняя переменная, вызов метода будет использовать
текущее

значение п
еM
ременной. За2ват внешнего контекста иначе называют
замыканием

(closure).

int
[]
a

= {1, 2, 3};

int

external

= 0;
//
внешняя

переменная

Transformer

int
,
int
�� t = x =

x + external;
//
замыкание

external

= 10;
// изменили переменную после описания t

int
[]
b

=
a
.
Transform
(
t
);
// прибавляет ол к каждому элементу

"ффектный приѐм использования замыканий


функции, реализующие
мемоизацию.
Мемоизация

(
memoization)


это кэширование результатов вычи
сM
лений. В следующем примере описан метод расширения для строк, который
возвращает функцию подсчѐта встречаемости символа в строке.

public

static

Func

char
,
int
� FrequencyFunc(
this

string

text)

{


int
[] freq
=
new

int
[
char
.MaxValue];


foreach

(
char

c
in

text)


{


freq[c]++;




1

Если аргументов несколько, скобки нужно указывать.
К
огда лямбда
-
оператор не имеет
в2одны2 аргументов
,
у
казываются

пустые скобки.

62


}



�ch = freq[ch];

}


//
использование

частотной

функции

var

f =
"There is no spoon"
.FrequencyFunc();

Console
.
WriteLine
(
f
(
'o'
));

22. События

События



способ описания связи одного объекта с другими по действиям.
Работу с событиями можно условно разделить на три этапа:



объявление

события (publishing);



регистрация

получателя события (subscribing);



генерация

события (raising).

Событие можно объявит
ь в предела2 класса, структуры или интерфейса.
Базовый синтаксис объявления события следующий:

модификаторы

event

тип
-
делегата

имя
-
события
;

Ключевое слово
event

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

Фактически, события являются полями типа делегатов. При объявлении
события к
омпилятор добавляет в класс

или структуру

private
-
поле с именем
имя
-
события

и типом
тип
-
делегата
. Кроме этого, для обслуживания события
компилятор создаѐт два метода
add_
Name
()

и
remove_
Name
()
, где
Name



имя с
оM
бытия. "ти методы содержат код, добавляющий и

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

Если программиста по каким
-
либо причинам не устраивает автоматическая
генерация методов
add_
Name
()

и
remove_
Name
()
, он может описать собственную
реализацию данны2 методов.
Для этого при объявлении события указывается
блок, содержащий секции
add

и
remove
:

модификаторы

event

тип
-
делегата

имя
-
события

{


add

{

операторы

}


remove

{

операторы

}

};

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

Для генерации события в требуемом месте кода помещается вызов в фо
рM
мате
имя
-
события
(
фактические
-
аргументы
)
. Предварительно обычно

проверяют,
63

назначен ли обработчик события. Генерация события может проис2одить тол
ьM
ко в том же классе, в котором событие объявлено
1
.

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

public

delegate

void

Handler
(
int

val
);


public

class

ExampleClass

{


private

int

_field;



public

int

Field


{





{



_field;



}




{


_field =
value
;


if

(
value

0)


{


// проверка нужна, чтобы предотвратить генерацию


// исключительной ситуации
, если нет
обработчика


if

(

!=
null
)


{


value
);


}


}


}


}



public

event

Handler


}

Рассмотрим

этап

регистрации

получателя

события
.
"тобы отреагировать
на событие, его надо ассоциировать с
обработчиком события
. Обработчиком
может быть метод, приводимый к типу события (делегату). В качестве обрабо
тM
чика может выступать анонимный метод или лямбда
-
оператор. Назначение и
удаление о
бработчиков события выполняется при помощи операторов
+=

и
-
=
.

Используем класс
ExampleClass

и продемонстрируем назначение и удал
еM
ние обработчиков событий:




1

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

заставляет компилятор проверять, что описание и генерация события
проис2одят в одном классе, и запрещает для события все операции, кроме
+=

и
-
=
.

64

public

class

MainClass

{


public

static

void

Reaction(
int

i)


{


Console
.WriteLine(
"Negative value = {0}"
, i);


}



public

static

void

Main()


{


var

c =
new

ExampleClass
();


c
.
Field

=
-
10;

//
нет

обработчиков
,
нет

реакции

на

событие



//
назначаем

обработчик





c.Field =
-
20;
//
вывод
: "Negative value =
-
20"



// назначаем ещ
ё

один обработчик в виде лямбда
-
выражения


Console
.WriteLine(i);


c.Field =
-
30;
//
вывод
: "Negative value =
-
30"
и

"
-
30"



//
удаляем

первый

обработчик


-
= Reaction;


}

}

Платформа .NET предлагает средства стандартизации работы с событиями.
В частности, для типов событий зарезервированы следующие делегаты:

public

delegate

void

EventHandler
(
object

sender
,
EventArgs

e
);


public

delegate

void

EventHandler
&#xT000;T(
object

sender, T e)


where

T

:
EventArgs
;

Как видим, данные делегаты предполагают, что первым параметром будет
выступать объект, в котором событие было сгенерировано. Второй параметр
используется для передачи информации события. "то либо класс
EventArgs
,
либо наследник этого класса с необ2одимыми
полями.

Сама генерация события обычно выносится в отдельный виртуальный м
еM
тод класса. В этом методе проверяется, был ли установлен обработчик события.
Также можно создать копию события перед обработкой (это актуально в мн
оM
гопоточны2 приложения2)

Внесѐм изм
енения в код класса
ExampleClass
, чтобы работа с событиями
соответствовала стандартам:

public

class

MyEventArg
s

:
EventArgs

{


public

int

NewValue {
;
private

; }



public

MyEventArg
s
(
int

newValue)

65


{


NewValue = newValue;


}

}


pu
blic

class

ExampleClass

{


private

int

_field;



public

int

Field


{



{

_field; }




{


_field =
value
;


if (
value

0)


{


new

MyEventArgs
(
value
)
)
;


}


}


}



protected

virtual

void

MyEventArg
s

e
)


{


EventHandler

MyEventArg
s


if

(local !=
null
)


{


local(
this
, e);


}


}



public

e
vent

EventHandler

MyEventArg
s

}

Создадим несколько полезны2 классов для упрощения работы с событи
яM
ми. Очень часто для передачи информации события достаточно класса с еди
нM
ственным свойством. В этом случае можно использовать универсальный
класс
EventArgs
&#xT000;T
.

public

class

EventArgs

T
� :
EventArgs

{


public

T EventInfo {
;
private

; }



public

EventArgs(T eventInfo)


{


EventInfo = eventInfo;


}

}

Вот пример использования
EventArgs
T&#xT100;

при описании события:

66

public

event

EventHandler

EventArgs

int

П
оместим
в

класс
EventHelper

два метода расширения, упрощающи2 ген
еM
рацию событий:

public

static

class

EventHelper

{


public

static

void

&#xT000;RaiseT(


this

EventHandler

EventArg
s
&#xT000;&#xT000;T handler,


EventArgs
&#xT000;T

args,
object

sender =
null
)


{


var

local

= handler;


if

(
local

!=
null
)


{


local
(sender, args);


}


}



public

static

void

Raise(
this

EventHandler

handler,


EventArgs

args,
object

sender =
null
)


{


var

local

= handler;


if

(
local

!=
null
)


{


local
(
sender
,
args
);


}


}

}

Вот пример использования метода расширения из класса
EventHelper
:

protected

virtual

void

EventArgs

int
� e)

{



}

23.
Перегрузка

операций

Язык C# позволяет организовать для объектов пользовательского класса
или структуры
перегрузку операций
. Могут быть
перегружены унарные опер
аM
ции
+
,
-
,
!
,
~
,
++
,
--
,
true
,
false

и бинарные операции
+
,
-
,
*
,
/
,
%
,
&
,
|
,
^
,

,
��
,
==
,
!=
,

,

,
�=
,
=
. При перегрузке бинарной операции автоматически перегр
уM
жается соответствующая операция с присваиванием (например, при перегрузке
операции
+

перегрузится и операция
+=
). Некоторые операции могут быть пер
еM
гружены только парами:
==

и
!=
,


и

,
�=

и
=
,
true

и
false
.

Для перегрузки операций используется специальн
ый статический метод,
имя которого образовано из ключевого слова
operator

и знака операции. Кол
иM
чество формальны2 параметров метода зависит от типа операции: унарная оп
еM
67

рация требует одного параметра, бинарная


дву2. Метод обязательно должен
иметь модифик
атор доступа
public
.

Рассмотрим перегрузку операций на примере. Определим класс для пре
дM
ставления точек на плоскости с перегруженной операцией сложения:

public

class

Point

{


public

double

X {
;
; }


public

double

Y {
;
; }



public

ov
erride

string

ToString()


{



String
.Format(
"X = {0} Y = {1}"
, X, Y);


}



public

static

Point

operator

+(
Point

a,
Point

b)


{



//
для простоты не делаем
проверк
у

аргументов на null




new

Point

{X = a.X + b.X, Y = a.Y + b.Y};


}

}

Для объектов класса
Point

возможно использование следующего кода:

var

p1 =
new

Point

{X = 10, Y = 20};

var

p2 =
new

Point

{X =
-
5, Y = 10};

Console
.WriteLine(p1);
//
печатает

"X = 10 Y = 20"

Console
.WriteLine(p2);
//
печатает

"X =
-
5 Y = 10"

Console
.WriteLine(p1 + p2);
//
печатает

"X = 5 Y = 30"

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

Дополним класс
Point
, позволив прибавлять к точке вещественное число:

public

class

Point

{


. . .


public

static

Point

operator

+
(
Point

a,
double

delta)


{



new

Point

{X = a.X + delta, Y = a.Y + delta};


}

}

68

Любой класс или структура могут перегрузить операции
true

и
false
.
Операции перегружаются парой, тип
возвращаемого значения операций


б
уM
лев. Если выполнена подобная перегрузка, объекты могут использоваться как
условия в оператора2 условного пере2ода и циклов.

Рассмотрим следующий пример. Пусть в классе
Point

перегружены опер
аM
ции
true

и
false
:

public

clas
s

Point

{


. . .


public

static

bool

operator

true
(
Point

a)


{



��(a.X 0) || (a.Y 0);


}



public

static

bool

operator

false
(
Point

a)


{


// этот метод должен возвращать true,


// если семантика объекта соотв
етствует false



(
a
.
X

== 0) && (
a
.
Y

== 0);


}

}

Теперь возможно написать такой код (обратите внимание на оператор
if
):

var

p =
new

Point

{X = 10, Y = 20};

if

(p)


Console
.WriteLine(
"Point is positive"
);

else


Console
.WriteLine(
"Point
has non
-
positive data"
);

Если класс или структура
T

перегружают
true
,
false

и операции
&

или
|
, то
становится возможным вычисление булевы2 выражений по сокращѐнной с2
еM
ме. В этом случае:



выражение
x && y

транслируется в
T.
false
(x) ? x : T.&(x,y)
;



выраже
ние
x || y

транслируется в
T.
true
(x) ? x : T.|(x,y)
;

Любой класс или структура могут перегрузить операции для неявного и
явного приведения типов. При этом используется следующий синтаксис:

public

static

implicit

operator

целевой
-
тип
(
приводимый
-
тип

имя
)


public

static

explicit

operator

целевой
-
тип
(
приводимый
-
тип

имя
)

Ключевое слово
implicit

используется при перегрузке неявного привед
еM
ния типов, а ключевое слово
explicit



при перегрузке операции явного прив
еM
дения. Либо
целевой
-
тип
, либо
приводимый
-
тип

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

Поместим две перегруженны2 операции приведения в класс
Point
:

69

public

class

Point

{


. . .


public

static

implicit

operator

Point
(
double

x)


{



new

Point

{X = x};


}



public

static

explicit

operator

double
(
Point

a)


{



Math
.Sqrt(a.X * a.X + a.Y * a.Y);


}

}

Вот пример кода, использующего преобразование типов:

var

p = new
Point

{X = 3, Y = 4};

double

y = 10;

double

x

= (
double
)

p
;
//
явное

приведение

типов

p

=
y
;


// неявное приведение типов

Хотелось бы отметить, что разработчик должен ответственно под2одить к
вопросу перегрузки операций. Методы перегрузки в совокупности с методами
приведения типов могут
породить код, который трудно сопровождать.

24. ёнонимные типы

ёнонимные типы

(anonymous types), представленные в C# 3.P, позволяют
создавать новый тип, не декларируя его заранее, а описывая непосредственно
при создании переменной. Мотивом для введения анон
имны2 типов в специф
иM
кацию языка послужила работа с коллекциями в те2нологии LINQ. При обр
аM
ботке коллекций тип элементов результата может отличаться от типа элементов
ис2одной коллекции. Например, одна обработка набора объектов
Student

м
оM
жет привести к кол
лекции, содержащей имя студента и возраст. Другая обр
аM
ботка


к коллекции с именем и номером группы. В таки2 ситуация2 в стары2
версия2 C# нужно или заранее создать необ2одимое количество вспомогател
ьM
ны2 типов, или воспользоваться неким «мегатипом», содерж
ащим все возмо
жM
ные поля результатов. ёнонимные типы предлагают более элегантное решение.

Объявление анонимного типа использует синтаксис инициализатора об
ъM
ектов, предварѐнный ключевым словом
new
. Тип полей не указывается, а выв
оM
дится из начального значения
.

var

anonymous

=
new

{
a

= 3,
b

=
true
,
c

=
"
string

data
"
};

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

70

int

x

= 10;

// у анонимного типа будут поля x (со значением ол), b и c

var

anonymous =
new

{x, b =
true
, c =
"string data"
};

ёнонимный тип следует рассматривать как класс, состоящий из полей
только для чтен
ия. Кроме полей, други2 элементов анонимный тип содержать
не может. Два анонимны2 типа считаются эквивалентными, если у ни2 полн
оM
стью (вплоть до порядка) совпадают поля (имена и типы).

var

anonymous

=
new

{
a

= 3,
b

=
true
,
c

=
"
string

data
"
};

var

anotherAnonymous =
new

{a = 1, b =
false
, c =
"data"
};

anonymous

=
anotherAnonymous
;
// допустимое присваивание

Хотя анонимный тип задумывался как 2ранилище данны2 (концепция ан
оM
нимны2 типов близка к концепции кортежей), действия в анонимный тип мо
жM
но поместить, используя делегаты:

Action

int
�� m = x =
Console
.WriteLine(x);

var

anonymous =
new


anonymous
.
(3);

25. Пространства имѐн

Пространства имѐн

служат для логической группировки пользовател
ьM
ски2 типов. Применение
пространств имѐн обосновано в крупны2 программны2
проекта2 для снижения риска конфликта имѐн и улучшения структуры библи
оM
тек кода.

Синтаксис описания пространства имѐн следующий:

namespace

имя
-
пространства
-
имён

{


[компоненты
-
пространства
-
имён]

}

Компонентами пространства имѐн могут быть классы, интерфейсы, дел
еM
гаты, перечисления, структуры и другие пространства имѐн. Само пространство
имѐн может быть вложено только в другое пространство имѐн.

Если в разны2 места2 программы определено несколько про
странств имѐн
с одинаковыми именами, компилятор собирает компоненты из эти2 пр
оM
странств в общее пространство имѐн. Для этого необ2одимо, чтобы одноиме
нM
ные пространства имѐн на2одились на одном уровне вложенности в иерар2ии
пространств имѐн.

Для доступа к к
омпонентам пространства имѐн используется синтаксис
имя
-
пространства
-
имён
.
имя
-
компонента
.

Для использования в программе некоего пространства имѐн служит
д
иM
ректива

using
. Еѐ синтаксис следующий:

using

имя
-
пространства
-
имён
;

71

using

[имя
-
псевдонима
=
] имя
-
прос
транства[
.
имя
-
типа]
;

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

как
из текущего проекта, так и из подключѐнны2 к проекту сборок.

Рассмотрим некоторые тонкости при работе с пространствами имѐн. Пре
дM
положим, что создаѐтся проект, использующий внешние сборки
A.dll

и
B.dll
.
Пусть сборка
A.dll

содержит пространство имѐн
NS

с классом
C
, и сборка
B.dll

содержит такое же пространство и класс. Как поступить для доступа к разли
чM
ным классам
C

в коде? "ту проблему решает операция
::

и директива
extern

alias
. Во
-
первы2, сборкам
A.dl
l

и
B.dll

нужно назначит
ь

текстовые псевд
оM
нимы. В Visual Studio псевдоним для подключѐнной сборки можно установить
в свойства2 сборки. При использовании компилятора командной строки псе
вM
доним указывается с опцией ссылки на сборку
:

csc.exe program.cs
/r:A=A
.dll

/r:B=B.dll

Затем с элементами сборок можно работать следующим образом:

extern

alias

A;

extern

alias

B;


public

class

Program

{


private

static

void

Main()


{


var

a =
new

A::NS.
C
();


var

b =
new

B::NS.
C
();


}

}

Существует предопределѐнный псевдоним с именем
global

для все2 ста
нM
дартны2 сборок платформы .NET.

26. Генерация и обработка исключительны2 ситуаций

Опишем возможности по генерации и обработке исключительны2 ситу
аM
ций в языке C#. Для генерации исключительной

ситуации используется опер
аM
тор
throw
:

throw

[
объект
-
класса
-
исключительной
-
ситуации
]
;

Объект, указанный после
throw
, должен быть объектом класса исключ
иM
тельной ситуации. В C# классами исключительны2 ситуаций являются класс
System.
Exception

и все его наследники. В некоторы2 языка2 для .NET можно
(2отя и не рекомендуется) генерировать исключения, не являющиеся произво
дM
72

ными от
Exception
. В таком случае CLR автоматически поместит объект и
сM
ключения в оболочку класса
RuntimeWrappedException
, кото
рый наследуется от
Exception
.

Класс
Exception



это базовый класс для представления исключительны2
ситуаций. Основными элементами этого класса являются:



свойство только для чтения
Message
, содержащее строку с описанием
ошибки;



перегруженный конструктор

с одним параметром
-
строкой, записыва
еM
мым в свойство
Message
;



строковое свойство
StackTrace
, описывающее содержимое стека вызова,
в которой первым отображается самый последний вызов метода.



свойство
InnerException



объект
Exception
, описывающий ошибку
, в
ыM
зывающую текущее исключение.



коллекция
-
словарь
Data

с дополнительной информацией об ошибке.

В пространстве имѐн
System

содержится несколько классов для описания
наиболее распространѐнны2 исключений
. Упомян
е
м некоторые из ни2
:

1.
ArgumentException



генерируется, когда методу передаѐтся недопуст
иM
мый аргумент.

2.
ArgumentNullException

(наследник
ArgumentException
)


генерируется,
когда методу передаѐтся аргумент, равный
null
.

3.
ArgumentOutOfRangeException

(наследник
ArgumentException
)


генер
иM
руетс
я,
если

методу передаѐтся аргумент, вы2одящий за допустимый диапазон.

4.
In
dexOutOfRange
Exception



выбрасывается при попытке обратиться к
элементу массива

по индексу, который вы2одит за границы массива
.

5
.
Invalid
Cast
Exception



генерируется, когда явное
преобразование типов
завершается неудачей
.

6
.
InvalidOperationException



сигнализирует о том, что состояние объе
кM
та препятствует выполнению метода (пример: запись в файл, который открыт
только для чтения).

7
.
NotSupportedException



сигнализирует о том, ч
то функциональная во
зM
можность не поддерживается.

8
.
NotImplementedException



сигнализирует о том, что функциональная
возможность не реализована.

9
.
ObjectDisposedException



генерируется, когда метод вызывается у уд
аM
лѐнного из памяти объекта.

Разработчик
может создать собственный класс для представления инфо
рM
мации об исключительной ситуации. Единственным условием для этого класса
в C# является прямое или косвенное наследование от класса
Exception
.

Рассмотрим пример программы с генерацией исключительной сит
уации:

using

System;


public

class

ExampleClass

{

73


private

int

_field;



public

int

Field


{



{

_field; }




{


if

(
value

0)


{


// объект
исключения

создаётся

"на месте"


throw

new

ArgumentOutOfRangeException
();


}


_field =
value
;


}


}

}


public

class

MainClass

{


public

static

void

Main()


{


var

a

=
new

ExampleClass
();


a
.
Field

=
-
3;

// ИС
генерируется, но не обрабатывается!


}

}

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

Опишем возможности по обработке исключительны2 си
туаций. Для пер
еM
2вата исключительны2 ситуаций служит блок
try



catch



finally
. Синта
кM
сис блока следующий:

try

{


[операторы,
-
способные
-
вызвать
-
исключительную
-
ситуацию]

}

[один
-
или
-
несколько
-
блоков
-
catch
]

[
finally

{


операторы
-
из
-
секции
-
завершения

}
]

Операторы из части
finally

(если она присутствует) выполняются всегда,
вне зависимости от того, произошла исключительная ситуация или нет. Если
один из операторов, расположенны2 в блоке
try
, вызвал исключительную сит
уM
ацию, управление немедленно передаѐтс
я на блоки
catch
. Синтаксис отдельн
оM
го блока
catch

следующий:

74

catch

[
(
тип
-
ИС [идентификатор
-
объекта
-
ИС]
)
]

{


операторы
-
обработки
-
исключительной
-
ситуации

}

Здесь
идентификатор
-
объекта
-
ИС



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

надо
. Если указать в
блоке
catch

оператор
throw

без аргумента,

это приведѐт к тому, что обрабат
ыM
ваемая исключительная ситуация будет сгенерирована повторно.

Моди
фицируем программу, описанную выше, добавив в
неѐ

блок пере2в
аM
та ошибки:

public

static

void

Main()

{


var

a =
new

ExampleClass
();


try


{


Console
.
WriteLine
(
"
This line is printed always
"
);


a
.
Field

=
-
3;


Console
.
WriteLine
("
This line is not printed if the error
"
);


}


catch

(
ArgumentOutOfRangeException

ex)


{


Console
.WriteLine(ex.Message);


}


catch

(
Exception

ex)


{


Console
.WriteLine(ex.Message);


}


finally


{


Console
.
WriteLine
("
This line is printed always

(
finally
)"
);


}

}

Если используется несколько блоков
catch
, то обработка исключительны2
ситуаций должна вестись по принципу «от частного


к общему», так как после
выполнения одного блока
catch

управление передаѐтся на часть
finally

(при
отсутствии
finally



на оператор после
try



catch
). Компилятор C# не позв
оM
ляет разместить блоки
catch

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

27.
Дир
ективы п
репроцессор
а

Директивы п
репроцессор
а



это инструкции для компилятора, начина
юM
щиеся с символа
#

и расположенные в отдельной строке кода. В табл.
5

дано
описание директив и и2 действия.

75

Таблица
5

Д
ирективы

препроцессора

Директива

Действие

#define

с
имвол

Определяет указанный символ

#undef

символ

Удаляет определение символа

#if

символ

[операция символ_2]
...

Тестирует, определѐн ли символ или набор
символов, связанны2 операциями
==
,
!=
,
&&
,
||

#else

ёльтернативная ветвь для
#if

#elif

символ

[операция символ_2]
...

Комбинация
#
else

и последующего
#
if

#endif

Окончание блока
#if

#warning

текст

Задаѐт текст предупреждения, генерируемого
компилятором

#error

текст

Текст ошибки, генерируемой компилятором

#line

[номер ["файл"]

|
hidden]

Позволят п
ринудительно задать номер строки
в ис2одном коде

#region

[имя]

Начало региона кода, который можно «све
рM
нуть» в редакторе

#endregion

Окончание региона кода

Наиболее часто применяются условные директивы, которые дают возмо
жM
ность включить или
проигнорировать участок кода при компиляции. В след
уM
ющем примере вызов
Console
.WriteLine()

будет компилироваться, пока уст
аM
новлена директива
#
define

DEBUG
:

#define

DEBUG


public

class

MyClass

{


private

int

_
x;



private

void

Foo()


{

#if

DEBUG


Console
.WriteLine(
"
Test
: x = {0}"
,
_
x);

#
endif


}

}

Символы для условной компиляции можно описать не только с помощью
#
define
, но и указав ключ компилятора командной строки
/
define

или испол
ьM
зуя окно свойств проекта в Visual Studio (в эти2 случа
я2 описание символа ра
сM
пространяется не на отдельный файл, а на всю сборку).

28. Документирование ис2одного кода

Язык
C# позволяет при написании программы снабжать ис2одный код
особыми
документирующими комментариями
. Содержимое документирующи2
комментариев

может затем быть выделено и обработано. Так реализуется ко
нM
76

цепция, при которой сам ис2одный код содержит необ2одимую документацию,
описывающую его.

Рассмотрим общие принципы документирования кода. Документирующие
комментарии


это
либо однострочные
коммен
тарии, начинающиеся с послед
оM
вательности
///
,
либо

блочные комментарии,
начинающиеся с последовател
ьM
ности
/
**
.

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


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


и

, то они заменяются после
довательностями
<

и
>
. В случае ссылок на универсальные шаблоны имя параметра
-
типа может
быть записано в фигурны2 скобка2

{

и
}
.

Таблица
6

Документирующие теги

Тег и синтаксис

Описание

c&#x-3c7;
text
/c&#x-3/7;윀

code&#x-3c7;&#xo-3d;繰
content
/code/7c;&#x-3o7; -3e;瀀

Позволяют вставить в комментари
й текст, являющийся
кодом. Второй тег применяется при необ2одимости вст
аM
вить несколько строк кода

example&#x-3e7;&#xx-3a;m7p;&#x-3l7;-30;


description

/example&#x-3/7;-3x;ާm;&#x-3p7;&#xl-3e;瀀

Помечает части комментария, являющиеся примером.
Обычно данный тег включает тег
code&#x-3c-;o7d;&#x-3e7;

exception cref="member"&#x-3e7;&#xx-3c;ߧp;&#x-3t7;&#xi-3o;n7 ;&#x-3c7;&#xr-3e;߷=;&#x-3"7;&#xm-3e;m7b;&#x-3e7;&#xr-3";瀀


description

/exception&#x-3/7;-3x;籾&#x-3p7;&#xt-3i;o7n;&#x-300;

П
оказывает, какие исключения мо
жет

сгенерировать

м
еM
тод
. ётрибут
cref

указыва
е
т на существующий тип ИС

para&#x-3p7; -3r;穰
content
/para/7p;&#x-3a7;&#xr-3a;瀀

Используется для визуального оформления


выделяет
параграф

param name="name"&#x-3p7; -3r;ާm;&#x-3 7;&#xn-3a;m7e;&#x-3=7;&#x"-3n;ާm;&#x-3e7;&#x"-30;


description

/param&#x-3/7;&#xp-3a;r7a;&#x-3m7;

Описывает

параметр

метода

paramref name="name"/&#x-3p7; -3r;ާm;&#x-3r7;-3f; 7n;&#x-3a7;&#xm-3e;=7";&#x-3n7; -3m;ߧ";&#x-3/7;

Указывает, что элемент комментария является не просто
словом, а параметром метода

remarks&#x-3r7;-3m;ާr;&#x-3k7;&#xs-30;


description

/remarks&#x-3/7;&#xr-3e;m7a;&#x-3r7;&#xk-3s;瀀

Содержит дополнительное описание

returns&#x-3r7;-3t;u7r;&#x-3n7;&#xs-30;


description

/returns&#x-3/7;&#xr-3e;t7u;&#x-3r7;&#xn-3s;瀀

Описание
возвращаемого значения метода

see cref="member"/&#x-3s7;-3e; 7c;&#x-3r7;-3f;=7";&#x-3m7;-3m;签&#x-3r7;&#x"-3/;瀀

seealso cref="mem"/&#x-3s7;-3e;ާl;&#x-3s7;&#xo-3 ;߇r;&#x-3e7;-3=;"7m;&#x-3e7;&#xm-3";/70;

Устанавливают ссылки на существующий тип или эл
еM
мент типа

summary&#x-3s7;&#xu-3m;m7a;&#x-3r7;&#xy-30;


description

/summary&#x-3/7;&#xs-3u;m7m;&#x-3a7;&#xr-3y;瀀

Содержит основное описание типа или элемента типа

typeparam name="name"&#x-3t7;&#xy-3p;ߧp;&#x-3a7;&#xr-3a;m7 ;&#x-3n7; -3m;ߧ=;&#x-3"7;&#xn-3a;m7e;&#x-3"7;


description

/typeparam&#x-3/7;&#xt-3y;p7e;&#x-3p7; -3r;ާm;&#x-300;

Позволяет указать описание
generic
-
параметра

value&#x-3v7; -3l;u7e;&#x-300;


property
-
description

/value&#x-3/7;&#xv-3a;l7u;&#x-3e7;

Используется для описания свойства

77

Ниже приведѐн фрагмент кода с документирующими комментариями.

&#xsumm; ry0;/// summary

///

/// /summary


&#xsee ; ref;&#x="Li;&#xst{T;&#x}"/0;/// see cref="List{T}"/

&#xpara;&#xm na;&#xme=";&#xstar;&#xtTim;out;&#x"000;/// param name="startTimeout"
The start timeout.
&#x/par; m00;/param

&#xpara;&#xm na;&#xme=";&#xsess;&#xionI; "00;/// param name="sessionId"
The session id.
&#x/par; m00;/param

public

bool

Start(
int

startTimeout,
int

sessionId) { . . . }

"тобы выделить документирующие комментарии из ис2одного
кода, мо
жM
но откомпилировать программу с ключом
/
doc
:file
, где
file



это имя XML
-
файла с комментариями. При работе
в

Visual Studio в свойства2 проекта дост
аM
точно установить флаг
Build | Output | Xml Documentation File
.

Заметим, что существуют самостоятельн
ые проекты, которые расширяют
возможности встроенной системы документирования кода. Упомян
е
м такие
проекты как
NDoc

и
Sandcastle
. Также достаточно популярным является
GhostDoc



дополнение к Visual Studio, облегчающее генерирование документ
иM
рующи2 коммента
риев.



78

Литература

1
. ёлба2ари, Дж.
C
# 3
.P. Справочник: Пер. с англ. / Дж. ёл
б
а2ари, Б. ё
лM
ба2ари.


3
-
е изд.


Спб.: БХВ
-
Петербург, 2PP9.


944

с
.: ил.

2
.
Ри2тер
,
Дж
. CLR via C#. Программирование на платформе Microsoft
.NET Framework 4.P на языке C# /
Дж.
Ри2тер.


3
-
е изд.


Спб.: Питер, 2P12.


928 с.: ил.

3
.

Троелсен, ". Язык программирования
C
# 2010
и платформа
.

4.0

/
".

Троелсен.


5
-
е изд.


М.: ООО «И.Д. Вильямс», 2P11.


1392 с.: ил.

4.
Хейлсберг
,
ё. Язык программирования
C
#
. Классика

Computers

Science
.
/
ё
.
Хейлсберг
,
М
.
Торгерсен, С. Вилтамут, П. Голд.


4
-
е изд.


Спб.: Питер,
2012.


784 с.: ил.

5
. Цвалина, К. Инфраструктура программны2 проектов: соглашения, ид
иM
омы и шаблоны для многократно используемы2 библиотек
.
. : Пер. с англ.
/
К
.
Цва
лина.


М.: ООО «И.Д. Вильямс», 2P11.


416 с.: ил.


Приложенные файлы

  • pdf 6741552
    Размер файла: 1 MB Загрузок: 0

Добавить комментарий