public xPos
class byte byte byte
yPos
ColorRectangle
width
height
0;
Rectangle
colorR; colorG;
слово s e a l e d позволяет застраховаться от ошибочного к л а с с о в , не п р е д н а з н а ч е н н ы х для в ы с т у п л е н и я в роли б а з о в ы х к л а с с о в .
А В Фролов, Г.
Фролов Язык
наследования
Самоучитель
Глава 4. Полиморфизм Н а р я д у с и н к а п с у л я ц и е й и н а с л е д о в а н и е м полиморфизм п р е д с т а в л я е т собой о д н у и з в а ж н е й ш и х к о н ц е п ц и й О О П . П р и м е н е н и е этой к о н ц е п ц и и п о з в о л я е т з н а ч и т е л ь но разработку сложных программ. Термин полиморфизм имеет греческое п р о и с х о ж д е н и е и о з н а ч а е т многих Н а р я д у с п р о г р а м м и р о в а н и е м этот т е р м и н и м е е т х о ж д е н и е в б и о л о г и и и х и м и и . В о д н о м из у ч е б н и к о в по б и о л о г и и мы н а ш л и такое о п р е д е л е н и е п о л и м о р физма: н а л и ч и е у одного вида н е с к о л ь к и х форм тела или т и п о в ок раски» [5]. В х и м и и этот т е р м и н применяется для о б о з н а ч е н и я с о е д и н е н и й , к о т о р ы е могут к р и с т а л л и з о в а т ь с я в р а з л и ч н ы х ф о р м а х . Я в л е н и е и з м е н е н и я к р и с т а л л и ч е с к о й с т р у к т у р ы о д н о г о и того же в е щ е с т в а при и з м е н е н и е в н е ш н и х у с л о в и й н а з ы в а е т с я в х и м и и п о л и м о р ф и з м о м [6]. Т и п и ч н ы й у г л е р о д , и м е ю щ и й две к р и с т а л л и ческие ф о р м ы . И г р а ф и т и а л м а з я в л я ю т с я у г л е р о д о м , но какая м е ж д у ними р а з н и ц а ! В п р о г р а м м и р о в а н и и с п о л и м о р ф и з м о м тесно связаны т а к и е п о н я т и я , как абст рактные классы, виртуальные методы, перегрузка методов и операторов. Частично пе р е г р у з к а м е т о д о в у ж е была р а с с м о т р е н а в п р е д ы д у щ е й главе. Здесь же вы у з н а е т е До¬ полнительные подробности.
полиморфизма Мы будем изучать п р и м е н е н и е п о л и м о р ф и з м а в п р о г р а м м а х С# на п р о с т о м п р и м е р е , имеющем некоторое отношение к геометрии. Пусть в нашем распоряжении имеются объекты различных типов т о ч к а , п р я м а я , п р я м о у г о л ь н и к , круг и т. д. Н а ш е й з а д а ч е й будет н а п и с а т ь классы С # , с п о м о щ ь ю к о т о р ы х п р о г р а м м а м о ж е т р и с о в а т ь эти ф и г у р ы на п л о с к о с т и .
Применение классов В н а ч а л е мы п о п ы т а е м с я р е ш и т ь эту задачу и з в е с т н ы м нам на д а н н ы й м о м е н т спосо¬ бом без и с п о л ь з о в а н и я п о л и м о р ф и з м а ( л и с т и н г 4.1). Листинг
4.1.
Файл
using System; namespace Shape class
Point
i n t x; n t у public
x, x;
t
i s
га 6
Язык С#
Самоучитель
у
int
y)
public
void
x,
int
y) точки
в
x,
x; у;
class
Rectangle
int int int int
x; y; w; h;
public
x,
int y,
int
int
h)
x;
this public
void
Draw(int
x,
int
y) прямоугольника
в
У) this
class
x;
ShapeApp
static
void
Point
pt
Rectangle
Мы
будем
args) new
rect
работать
с
Point(10,
new R e c t a n g l e ( l ,
точками
в классе P o i n t , а п р я м о у г о л ь н и к и
и
4,
10,
прямоугольниками.
20)
Точки
инкапсулируются
в классе R e c t a n g l e .
в н и м а н и е , как м ы и н и ц и а л и з и р у е м поля класса P o i n t в конструкторе: А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
public
Point ( i n t x,
int
y)
x; у; Здесь м ы н а м е р е н н о в ы б р а л и для п а р а м е т р о в к о н с т р у к т о р а имена, с о в п а д а ю щ и е с и м е н а м и с о о т в е т с т в у ю щ и х полей класса. При в ы п о л н е н и и п р и с в а и в а н и я нам н у ж н о п о м о ч ь к о м п и л я т о р у сделать р а з л и ч и е м е ж д у п а р а м е т р а м и метода и п о л я м и класса, называющимися одинаково. Эта задача р е ш а е т с я с п р и м е н е н и е м к л ю ч е в о г о слова это к л ю ч е в о е слово о з н а ч а е т т е к у щ и й о б ъ е к т класса P o i n t .
В данном контексте
Если бы п а р а м е т р ы к о н с т р у к т о р а и поля класса н а з ы в а л и с ь п о - р а з н о м у , в и с п о л ь зовании к л ю ч е в о г о слова н е было б ы н и к а к о й public x у
Point (int
int
yNew)
xNew; yNew;
Для того ч т о б ы п р о г р а м м а могла н а р и с о в а т ь т о ч к у и п р я м о у г о л ь н и к , мы о п р е д е л и ли в классах P o i n t и R e c t a n g l e м е т о д ы с н а з в а н и е м Draw. Эти м е т о д ы и м е ю т оди¬ н а к о в ы е п а р а м е т р ы , о д н а к о н е с к о л ь к о р а з л и ч а ю т с я п о своему д е й с т в и ю . И с х о д н ы й текст метода D r a w , п р е д н а з н а ч е н н о г о для р и с о в а н и я т о ч к и , п р е д с т а в л е н ниже: public
void
Draw(int
х,
int
у) точки
в
х,
у)
х;
С о б с т в е н н о р и с о в а н и е мы з а м е н я е м в ы в о д о м на к о н с о л ь строки с о о б щ е н и я о т о м , что т о ч к а н а р и с о в а н а в т а к о м - т о месте к о о р д и н а т н о й п л о с к о с т и . П о с л е этого м е т о д и з м е н я е т т е к у щ и е к о о р д и н а т ы т о ч к и . О б р а т и т е в н и м а н и е , что здесь м ы и с п о л ь з о в а л и к л ю ч е в о е слово t h i s , ч т о б ы к о м п и л я т о р смог сделать р а з л и ч и е м е ж д у н а з в а н и я м и п а р а м е т р о в метода и н а з в а н и я м и полей класса. Метод Draw, с помощью которого можно следующим образом: public
void
Draw(int
Console
х,
int
нарисовать прямоугольник, выглядит
у)
("Рисование
прямоугольника
в
х,
у; От п р е д ы д у щ е г о метода он о т л и ч а е т с я т е к с т о м с о о б щ е н и я , о т о б р а ж а е м о г о на кон¬ соли. Глава 4.
когда у нас есть классы с м е т о д а м и D r a w , м о ж н о п р и с т у п а т ь к р и с о в а н и ю фигур. Н а ш а п р о г р а м м а р и с у е т ф и г у р ы , создавая по очереди о б ъ е к т ы к а ж д о г о класса и в ы з ы в а я для этих о б ъ е к т о в м е т о д Draw: pt
new
Rectangle
rect
new R e c t a n g l e ( l ,
4,
10,
20);
В а ж н о , что в п е р в о м случае в ы з ы в а е т с я метод D r a w из класса P o i n t , а во вто¬ ром — м е т о д D r a w и з класса R e c t a n g l e .
обобщения с помощью наследования В
п р и м е р е мы имели д е л о всего с двумя ф и г у р а м и — точкой и п р я м о
у г о л ь н и к о м . В с е г о ж е , как и з в е с т н о из ш к о л ь н о г о курса г е о м е т р и и , с у щ е с т в у е т вели¬ кое м н о ж е с т в о р а з н ы х фигур. З а д а д и м с я в о п р о с о м , м о ж н о ли о б о б щ и т ь п о в е д е н и е р а з л и ч н ы х фигур в одном ба зовом к л а с с е , о т р а з и в о с о б е н н о с т и к а ж д о й фигуры в с о о т в е т с т в у ю щ е м п р о и з в о д н о м классе? С о з д а д и м б а з о в ы й класс S h a p e , п р и з в а н н ы й и н к а п с у л и р о в а т ь в себе о б щ и е эле м е н т ы п о в е д е н и я всех ф и г у р . Н а базе класса S h a p e с о з д а д и м классы P o i n t и R e c и н к а п с у л и р у ю щ и х в себе о с о б е н н о с т и п о в е д е н и я т о ч е к и п р я м о у г о л ь н и к о в . И с х о д н ы й т е к с т п р о г р а м м ы , р е а л и з у ю щ е й д а н н у ю с т р у к т у р у к л а с с о в , представлен в л и с т и н г е 4.2. Листинг
4.2.
1
Файл
cs
System; namespace Shapel class
Shape
protected protected public xPos xPos
class
xPos; yPos; x,
int
y)
x; y;
Point
public
int int
Shape
P o i n t ( i n t x,
i n t y)
base
A
(x,
y)
Фролов, Г. R Фролов. Язык
Самоучитель
public
void
x,
int
y) точки
xPos xPos
class
в
x,
у)
x; y;
Rectangle
Shape
int int public
x,
width height public
int
int w,
int
h)
base(x,
w; h;
void
x,
int
y) прямоугольника
x, xPos yPos
class
y)
в
у) х; у;
ShapelApp
static
void
Point
pt
Rectangle
args) new
rect
new
4,
10,
20);
Что же нам у д а л о с ь о б ъ е д и н и т ь в базовом классе S h a p e т а к о г о , что я в л я е т с я ха р а к т е р н ы м для фигур л ю б о г о типа? К а ж д а я фигура и м е е т свои к о о р д и н а т ы н а п л о с к о с т и . П о л о ж е н и е точки о д н о з н а ч н о к о о р д и н а т а м и по осям X и Y, а п о л о ж е н и е п р я м о у г о л ь н и к а мы о п р е д е по к о о р д и н а т а м его л е в о г о н и ж н е г о угла.
Глава 4. Полиморфизм
С о о т в е т с т в е н н о в базовом классе S h a p e мы о п р е д е л и л и поля x P o s и y P o s , пред¬ н а з н а ч е н н ы е д л я х р а н е н и я к о о р д и н а т фигуры (не в а ж н о , какой и м е н н о ) н а п л о с к о с т и . В этом же классе имеется к о н с т р у к т о р , и н и ц и а л и з и р у ю щ и й д а н н ы е поля: class
Shape
p r o t e c t e d i n t xPos p r o t e c t e d int yPos; public S h a p e ( i n t x, xPos xPos
int
y)
x; y;
Что же касается м е т о д а , в ы п о л н я ю щ е г о р и с о в а н и е ф и г у р , то он для к а ж д о й фигуры свой. П о э т о м у мы не с м о ж е м о п р е д е л и т ь м е т о д D r a w в б а з о в о м к л а с с е , а в ы н у ж д е н ы создавать свой м е т о д D r a w в к а ж д о м п р о и з в о д н о м классе. Р а с с м о т р и м класс P o i n t , с о з д а н н ы й н а базе класса S h a p e : class
Point
public
Shape
P o i n t ( i n t x,
int y)
public
x,
base
(x,
int точки
xPos xPos
y)
в
x,
x; y;
Как в и д и т е , в нем о п р е д е л е н к о н с т р у к т о р , в ы з ы в а ю щ и й к о н с т р у к т о р базового ме¬ тода с д в у м я п а р а м е т р а м и . Н а л и ч и е т а к о г о к о н с т р у к т о р а н е о б х о д и м о для т о г о , ч т о б ы о т м е н и т ь вызов к о н с т р у к т о р а по у м о л ч а н и ю базового класса без п а р а м е т р о в и в ы з в а т ь н у ж н ы й к о н с т р у к т о р базового класса с д в у м я п а р а м е т р а м и . М е т о д D r a w класса P o i n t и м и т и р у е т р и с о в а н и е точки в ы в о д о м н а консоль соот¬ ветствующего сообщения. А н а л о г и ч н о у с т р о е н и класс R e c t a n g l e : class int int
Rectangle
Shape
width;
public width height
166
x, -
int
y,
int
int
h)
base(x,
y)
w; h; Г,
Фролов.
С#. Самоучитель
public
void
x,
Console xPos x; yPos
int
y) прямоугольника
в
x,
Этот класс также создан на базе класса S h a p e . По с р а в н е н и ю с б а з о в ы м к л а с с о м мы д о б а в и л и в него два н о в ы х п о л я , х р а н я щ и х р а з м е р ы п р я м о у г о л ь н и к а , и з м е н и л и к о н с т р у к т о р и метод Draw. К о н с т р у к т о р п р о и з в о д н о г о класса R e c t a n g l e вначале в ы з ы в а е т к о н с т р у к т о р ба¬ зового класса, с о х р а н я я в с о о т в е т с т в у ю щ и х полях ( x P o s и x P o s ) к о о р д и н а т ы распо¬ л о ж е н и я п р я м о у г о л ь н и к а н а п л о с к о с т и . Д а л е е этот к о н с т р у к т о р и н и ц и а л и з и р у е т поля w i d t h и h e i g h t , хранящие размеры прямоугольника. П о с л е п р и м е н е н и я н а с л е д о в а н и я с п о с о б р а б о т ы с к л а с с а м и , о п р е д е л е н н ы м и в на¬ шей п р о г р а м м е , никак не и з м е н и л с я по с р а в н е н и ю с п р е д ы д у щ е й п р о г р а м м о й (см. ли¬ стинг 4.1): Point
pt
Rectangle
new
rect
new
4,
10,
П о - п р е ж н е м у для р и с о в а н и я фигур нам н у ж н о создавать два о б ъ е к т а р а з н ы х клас¬ сов, в ы з ы в а я для к а ж д о г о из них свой м е т о д D r a w . П р и м е н е н и е н а с л е д о в а н и я д о н е к о т о р о й степени у л у ч ш и л о структуру н а ш е й про¬ г р а м м ы , так как нам у д а л о с ь в ы н е с т и в базовый класс все о б щ е е , что есть у н а ш и х фи¬ гур. О д н а к о на этом с о в е р ш е н с т в о еще не д о с т и г н у т о — мы не м о ж е м и с п о л ь з о в а т ь базовый класс для р и с о в а н и я фигур.
Виртуальные методы В п р е д ы д у щ е й главе мы р а с с к а з ы в а л и вам о в о з м о ж н о с т и п е р е о п р е д е л е н и я м е т о д о в базового класса в п р о и з в о д н о м классе. Тогда же мы у п о м и н а л и к л ю ч е в о е слово n e w , с п о м о щ ь ю к о т о р о г о н у ж н о о т м е т и т ь в п р о и з в о д н о м классе п е р е о п р е д е л е н н ы й м е т о д , чтобы он и с п о л ь з о в а л с я в м е с т о м е т о д а с тем же и м е н е м , но о б ъ я в л е н н ы м в б а з о в о м М о ж н о ли и с п о л ь з о в а т ь д а н н у ю т е х н и к у , ч т о б ы р и с о в а т ь ф и г у р ы с п о м о щ ь ю ме¬ тода D r a w , о б ъ я в л е н н о г о в б а з о в о м классе? Н е т , так как м е т о д д о ч е р н е г о класса, о б ъ я в л е н н ы й с к л ю ч е в ы м словом n e w , полно¬ стью с к р ы в а е т о д н о и м е н н ы й м е т о д базового класса. Н а м же т р е б у е т с я , чтобы про¬ грамма как-то и с п о л ь з о в а л а м е т о д D r a w базового класса чернего класса. Глава 4. Полиморфизм
для р и с о в а н и я фигуры до¬
Рассмотрим следующий кода, и с п о л ь з у ю щ и й н у ж н у ю нам ф у н к ц и о н а л ь ность (здесь м ы п р е д п о л а г а е м , что п р о и з в о д н ы е классы P o i n t и R e c t a n g l e о б р а з о ваны о т о д н о г о базового класса S h a p e ) : Shape
pt
new
Shape
rect
25);
new
4,
10,
20);
О б р а т и т е в н и м а н и е , что мы создаем два объекта дочерних к л а с с о в , з а п и с ы в а я ссылки на эти о б ъ е к т ы в п е р е м е н н у ю с т и п о м базового класса. Такая о п е р а ц и я вполне д о п у с т и м а в я з ы к е С#. Далее мы и с п о л ь з у е м о б ъ е к т ы базового класса для р и с о в а н и я р а з н ы х ф и г у р , вызы¬ вая м е т о д D r a w , о б ъ я в л е н н ы й в б а з о в о м классе. Чтобы т а к о е стало в о з м о ж н ы м , н е о б х о д и м о в б а з о в о м и д о ч е р н е м классах объ явить метод D r a w с п е ц и а л ь н ы м о б р а з о м ( л и с т и н г 4.3). Листинг
4.3.
using System; namespace ShapeVirtual class
Shape
protected protected public
S h a p e ( i n t x,
xPos xPos
public
class
-
int
y)
x;
virtual
Point
void
x,
int
y)
Shape
public
P o i n t ( i n t x,
public
override
xPos
168
int int
void
i n t y)
base
Draw(int
x,
(x,
y)
int
y)
точки
в
x,
y;
А В Фролов Г В. Фролов Язык
Самоучитель
class
Rectangle
int
Shape
width;
public
x,
width height
public
int y,
int w,
int
h)
void
Draw(int
x,
int
y)
прямоугольника
class
y)
w; h;
override
x,
base(x,
в
У)
XPOS
X;
yPos
у;
ShapeVirtualApp
static
void
args)
Shape
pt
new 7);
Shape
rect
new R e c t a n g l e ( l ,
allShapes
4,
10,
20)
new
new new new new
3, 2,
currentShape in 0);
1, 3,
allShapes)
П р е ж д е всего нам н у ж н о о п р е д е л и т ь б а з о в ы й класс S h a p e , п р е д с т а в л я ю щ и й собой общие и
свойства
фигур.
Rectangle.
Глава 4. Полиморфизм
От
этого
класса
будут
наследоваться
классы
Point
В б а з о в о м классе S h a p e , п о м и м о полей x P o s и y P o s , нам н у ж н о о б ъ я в и т ь конст¬ р у к т о р и так н а з ы в а е м ы й class
Draw:
Shape
protected protected public
xPos; yPos;
Shape ( i n t x,
xPos
int
y)
y;
public
Тело
int int
virtual
void
виртуального
x,
метода
Draw,
int
объявленного
с
помощью
ключевого
слова
не с о д е р ж и т ни о д н о г о о п е р а т о р а . Во в р е м я в ы п о л н е н и я п р о г р а м м ы этот в и р т у а л ь н ы й м е т о д б у д е т п о д м е н я т ь с я на о д н о и м е н н ы й м е т о д из п р о и з в о д н ы х клас¬ сов P o i n t и R e c t a n g l e . Н и ж е м ы п р и в е л и о б ъ я в л е н и е п р о и з в о д н о г о класса P o i n t : class
Point
Shape
public
P o i n t ( i n t x,
public
override
i n t y)
base
void
x,
(x,
y)
int точки в
xPos
x,
у;
О б р а т и т е в н и м а н и е , что в этом к л а с с е , так же как и в б а з о в о м классе S h a p e , име¬ ется м е т о д D r a w , п е р е о п р е д е л я ю щ и й м е т о д базового класса. О д н а к о э т о т м е т о д о б ъ я в л е н в п р о и з в о д н о м классе с и с п о л ь з о в а н и е м к л ю ч е в о г о слова override. А н а л о г и ч н ы й м е т о д о б ъ я в л е н и в п р о и з в о д н о м классе R e c t a n g l e : public
override
void
Draw(int
x,
int
y)
прямоугольника xPos yPos
в
x,
X; у;
А В. Фролов, Г. В. Фролов
СМ. Самоучитель
Н а п о м н и м , что ранее в п р и в е д е н н ы х п р и м е р а х п р о г р а м м мы у ж е п е р е о п р е д е л я л и м е т о д ы базового класса, п о л ь з у я с ь для этого к л ю ч е в ы м словом n e w . В р е з у л ь т а т е к о м п и л я т о р скрывал о т п р о г р а м м ы п е р е о п р е д е л е н н ы й метод б а з о в о г о класса. П р и м е н е н и е к л ю ч е в о г о слова o v e r r i d e д а е т д р у г о й э ф ф е к т . В и р т у а л ь н ы й ме¬ тод базового класса п о д м е н я е т с я с о о т в е т с т в у ю щ и м м е т о д о м п р о и з в о д н о г о класса не во время к о м п и л я ц и и , а во время работы программы. Таким о б р а з о м , в с л е д у ю щ е м ф р а г м е н т е кода в и р т у а л ь н ы й м е т о д D r a w б а з о в о г о класса S h a p e п о д м е н я е т с я м е т о д о м D r a w п р о и з в о д н о г о класса P o i n t : Shape
pt
new 7
Point(10,
25);
Это п р о и с х о д и т п о т о м у , что мы создали о б ъ е к т класса P o i n t и з а п и с ы в а е м ссыл¬ ку на него в п е р е м е н н у ю , и м е ю щ у ю тип базового класса S h a p e . Во в р е м я своего вы¬ п о л н е н и я п р о г р а м м а д и н а м и ч е с к и о п р е д е л я е т тип с с ы л к и , х р а н я щ е й с я в п е р е м е н н о й p t , и в ы з ы в а е т для нее м е т о д D r a w и з с о о т в е т с т в у ю щ е г о п р о и з в о д н о г о к л а с с а ( в дан¬ ном случае и з класса P o i n t ) . А н а л о г и ч н о в с л е д у ю щ е м ф р а г м е н т е кода вместо в и р т у а л ь н о г о метода D r a w базо¬ вого класса будет вызван м е т о д D r a w п р о и з в о д н о г о класса R e c t a n g l e : Shape
rect
new
4,
20);
Т е п е р ь , в о с п о л ь з о в а в ш и с ь п о л и м о р ф и з м о м , м ы сумели о р г а н и з о в а т ь о б р а б о т к у о б ъ е к т о в р а з л и ч н ы х п р о и з в о д н ы х к л а с с о в , в ы з ы в а я для этого один в и р т у а л ь н ы й метод базового класса. Такой п р и е м п о з в о л я е т у п р о с т и т ь п р о г р а м м у . О д н а к о н а и б о л е е о ч е в и д н ы м и эти у п р о щ е н и я с т а н о в я т с я при н е о б х о д и м о с т и обра¬ б а т ы в а т ь о б ъ е к т ы р а з л и ч н ы х п р о и з в о д н ы х к л а с с о в , р а з м е щ е н н ы е в м а с с и в а х или к о н различных типов. В
следующем фрагменте
объектов
п р о г р а м м ы мы
создали
массив для хранения четырех
базового класса S h a p e : allShapes
new
В с о з д а н н ы й таким о б р а з о м массив мы п о м е щ а е м д в е точки и два п р я м о у г о л ь н и к а : allShapes[l] allShapes[2]
new new R e c t a n g l e ( 2 , new new
3, 2,
1, 3,
5) 4);
В р е з у л ь т а т е наш массив б у д е т с о д е р ж а т ь о б ъ е к т ы д в у х р а з н ы х п р о и з в о д н ы х клас¬ сов, и м е ю щ и х о б щ и й базовый к л а с с S h a p e . Д о п у с т и м , нам н у ж н о н а р и с о в а т ь все ф и г у р ы , х р а н я щ и е с я в м а с с и в е . П р и исполь¬ зовании п о л и м о р ф и з м а это м о ж н о сделать п р о с т ы м в ы з о в о м в и р т у а л ь н о г о м е т о д а D r a w базового класса S h a p e : in
Глава 4.
allShapes)
171
Здесь для п р о с т о т ы все фигуры р и с у ю т с я в о д н о й т о ч к е с к о о р д и н а т а м и (0, 0), но вы м о ж е т е легко и з м е н и т ь цикл таким о б р а з о м , чтобы к а ж д а я фигура была н а р и с о в а н а на своем месте. О б р а щ а е м в а ш е в н и м а н и е н а с л е д у ю щ и й и н т е р е с н ы й факт. О б р а б а т ы в а я и с х о д н ы й текст п р о г р а м м ы , к о м п и л я т о р не м о ж е т з н а т ь , объекты ка¬ кого из п р о и з в о д н ы х классов и в какой п о с л е д о в а т е л ь н о с т и з а п и с а н ы в массиве. Эта и н ф о р м а ц и я с т а н о в и т с я д о с т у п н о й т о л ь к о в о время в ы п о л н е н и я п р о г р а м м ы . в и р т у а л ь н ы х функций как раз и п о з в о л я е т программе о п р е д е л и т ь тип о ч е р е д н о г о о б ъ е к т а , и з в л е к а е м о г о в цикле из массива. В з а в и с и м о с т и от т о г о , какой о б ъ е к т на о ч е р е д н о й итерации цикла п р о г р а м м а и з в л е к л а из м а с с и в а (точку или пря¬ м о у г о л ь н и к ) , она в ы з ы в а е т м е т о д D r a w и з с о о т в е т с т в у ю щ е г о п р о и з в о д н о г о класса ( P o i n t или R e c t a n g l e ) .
Раннее и позднее связывание В п р о г р а м м и р о в а н и и и с п о л ь з у ю т с я два т е р м и н а , п о д х о д я щ и е под о п и с а н и е случаев перегрузки м е т о д о в с и с п о л ь з о в а н и е м к л ю ч е в ы х слов n e w и o v e r r i d e . При переопределении метода базового класса о д н о и м е н н ы м методом производного класса, и м е ю щ и м такой же список параметров и объявленным с п о м о щ ь ю ключевого сло¬ ва new, п р о и с х о д и т так называемое раннее связывание binding). Оно называется ранним, так как выполняется еще на стадии компиляции исходного текста программы. К о г д а же з а д е й с т в о в а н м е х а н и з м в и р т у а л ь н ы х ф у н к ц и й и в х о д е р а б о т ы програм¬ мы в и р т у а л ь н а я ф у н к ц и я базового класса п е р е о п р е д е л я е т с я ф у н к ц и е й п р о и з в о д н о г о класса, о б ъ я в л е н н о й как o v e r r i d e , в ы п о л н я е т с я позднее связывание (late binding). И м е н н о п о з д н е е с в я з ы в а н и е и п о з в о л я е т нам в ы з ы в а т ь в и р т у а л ь н ы е м е т о д ы базо¬ вого класса для о б р а б о т к и о б ъ е к т о в р а з л и ч н ы х п р о и з в о д н ы х к л а с с о в . К р о м е т о г о , п р и м е н я я п о л и м о р ф и з м и в и р т у а л ь н ы е м е т о д ы , вы м о ж е т е п р и м е н я т ь п р о г р а м м н ы й к о д , р а с с ч и т а н н ы й на и с п о л ь з о в а н и е б а з о в ы х к л а с с о в , для о б р а б о т к и о б ъ е к т о в произ¬ водных классов.
Абстрактные классы Если все м е т о д ы класса я в л я ю т с я в и р т у а л ь н ы м и , т. с. о б ъ я в л е н ы с к л ю ч е в ы м словом v i r t u a l , т о т а к о й класс н а з ы в а е т с я абстрактным. К а ж д ы й метод абстрактного класса должен быть переопределен соответствующим ме¬ тодом производного класса. Вы не можете создать объект абстрактного класса, так как фактически такой класс содержит только шаблоны методов, но не сами методы. В л и с т и н г е 4.4 мы п р и в е л и н о в ы й в а р и а н т п р о г р а м м ы , п р и м е н я ю щ е й полимор¬ физм для о б р а б о т к и о б ъ е к т о в , х р а н я щ и х с я в м а с с и в е . Листинг
4.4.
Файл
using System; namespace ShapeAbstract abstract class Shape
p r o t e c t e d int xPos; protected int yPos; p u b l i c S h a p e ( i n t x,
y)
x; у
x P о s abstract
class
int
public
Point
void
x,
int y)
Shape
public
Point(int
public
override
x,
int
y)
base
void
x,
(x,
int точки
xPos xPos
class int int
y)
y) в
x,
у)
base(x,
y)
x; y;
Rectangle
Shape
width;
public
x,
width height public
int y,
int
int
h;
override
void
x,
int
y)
прямоугольника xPos yPos
-
у) x; y;
class static
void
args) allShapes new new new new
4. Полиморфизм
new P 3, 2,
1, 3,
5) 4)
в
in
allShapes)
Теперь м ы о б ъ я в и л и класс S h a p e а б с т р а к т н ы м , у к а з а в при его о б ъ я в л е н и и к л ю ч е вое слово a b s t r a c t : abstract
class
Shape
protected int xPos; protected int yPos public S h a p e ( i n t x, xPos xPos
int
y)
x; y;
abstract
public
void
Draw(int
x,
int
y)
К р о м е т о г о , м ы о б ъ я в и л и а б с т р а к т н ы м и м е т о д D r a w н а ш е г о а б с т р а к т н о г о базово го
класса.
О б р а т и т е в н и м а н и е , что
объявление абстрактного
метода з а к а н ч и в а е т с я
т о ч к о й с запятой и не и м е е т тела, о г р а н и ч е н н о г о ф и г у р н ы м и с к о б к а м и . И с п о л ь з о в а н и е к л ю ч е в о г о слова a b s t r a c t при о б ъ я в л е н и и м е т о д а и с к л ю ч а е т не о б х о д и м о с т ь ( а т а к ж е в о з м о ж н о с т ь ) у к а з а н и я к л ю ч е в ы х слов o v e r r i d e и v i r t u a l . К р о м е т о г о , это к л ю ч е в о е с л о в о н е с о в м е с т и м о с к л ю ч е в ы м с л о в о м
Перегрузка операторов Рассмотренную
в
предыдущей
из с р е д с т в р е а л и з а ц и и С#.
Другое
такое
главе
полиморфизма,
средство
перегрузку
методов
можно
предусмотренных в языке
перегрузка
считать
одним
программирования
операторов.
Мы у ж е г о в о р и л и , что в ч и с л о в ы х и с т р о к о в ы х в ы р а ж е н и я х м о ж н о и с п о л ь з о в а т ь т а к и е о п е р а т о р ы , как о п е р а т о р с л о ж е н и я , в ы ч и т а н и я , у м н о ж е н и я , р а в е н с т в а и т. п. Я з ы к С # п о з в о л я е т вам п е р е о п р е д е л я т ь д е й с т в и е м н о г и х о п е р а т о р о в таким о б р а з о м , ч т о б ы их м о ж н о было и с п о л ь з о в а т ь при р а б о т е с л ю б ы м и т и п а м и д а н н ы х , в том числе с т и п а м и д а н н ы х , с о з д а в а е м ы х на о с н о в е р а з р а б о т а н н ы х вами к л а с с о в . Н и ж е п е р е ч и с л е н ы б и н а р н ы е о п е р а т о р ы , к о т о р ы е м о ж н о п е р е г р у ж а т ь в я з ы к е С#: -
*
I
А это с п и с о к п е р е г р у ж а е м ы х у н а р н ы х о п е р а т о р о в : true
false
Фролов, Г.
Фролов. Язык С#. Самоучитель
П е р е г р у з к а о п е р а т о р о в д а е т в о з м о ж н о с т ь с о з д а в а т ь более п р о з р а ч н ы е и п о н я т н ы е и с х о д н ы е тексты п р о г р а м м , так как для в ы п о л н е н и я м н о г и х д е й с т в и й с о п р е д е л е н н ы ми вами о б ъ е к т а м и м о ж н о и с п о л ь з о в а т ь п р и в ы ч н ы е о п е р а т о р ы . О б р а т и т е в н и м а н и е , что в я з ы к е С# нет в о з м о ж н о с т и п е р е г р у з и т ь о п е р а т о р при сваивания При и с п о л ь з о в а н и и этого о п е р а т о р а с о б ъ е к т а м и л ю б о г о типа происхо¬ д и т простое к о п и р о в а н и е ссылки на объект.
теория комплексных чисел Чтобы продемонстрировать использование перегруженных операторов, мы выбрали с п о с о б , п р е д л а г а е м ы й м н о г и м и у ч е б н и к а м и п о п р о г р а м м и р о в а н и ю . М ы с о з д а д и м соб с т в е н н ы й класс п р е д н а з н а ч е н н ы й для р а б о т ы с к о м п л е к с н ы м и ч и с л а м и . П о л н о е о п р е д е л е н и е к о м п л е к с н о г о ч и с л а м о ж н о найти в л ю б о м с п р а в о ч н и к е по м а т е м а т и к е , н а п р и м е р в [7], п о э т о м у мы о г р а н и ч и м с я т о л ь к о с а м ы м и н е о б х о д и м ы ¬ ми с в е д е н и я м и . Согласно [7], числом н а з ы в а е т с я число вида a + b i , где аи b п р е д с т а в л я ю т собой д е й с т в и т е л ь н ы е числа. С и м в о л i я в л я е т с я о б о з н а ч е н и е м мнимой еди ницы, у д о в л е т в о р я ю щ е й у с л о в и ю i2 = - l . Ч и с л а а и b н а з ы в а ю т с я с о о т в е т с т в е н н о действительной и мнимой ч а с т ь ю числа. Для к о м п л е к с н ы х чисел о п р е д е л е н ы о п е р а ц и и с р а в н е н и я , с л о ж е н и я , в ы ч и т а н и я , у м н о ж е н и я и д е л е н и я . И м е н н о эти о п е р а ц и и нас будут и н т е р е с о в а т ь в п е р в у ю о ч е р е д ь . Д а д и м н е с к о л ь к о н е о б х о д и м ы х о п р е д е л е н и й о п е р а ц и й , в ы п о л н я е м ы х над ком¬ плексными числами.
Сравнение Два к о м п л е к с н ы х ч и с л а равны между с о б о й , когда равны их д е й с т в и т е л ь н ы е и м н и м ы е части. Н а п р и м е р , если у нас есть два числа a 2 + b 2 i , т о они будут р а в н ы , если в ы п о л н я е т с я с л е д у ю щ е е у с л о в и е :
Здесь д л я о б о з н а ч е н и я у с л о в и я м ы и с п о л ь з о в а л и о п е р а т о р ы я з ы к а С#.
Сложение Ч т о б ы с л о ж и т ь два к о м п л е к с н ы х числа, надо с л о ж и т ь по о т д е л ь н о с т и их д е й с т в и т е л ь ¬ ные и м н и м ы е части:
Вычитание В ы ч и т а н и е к о м п л е к с н ы х чисел в ы п о л н я е т с я п о с л е д у ю щ е м у правилу: -
Умножение Для у м н о ж е н и я к о м п л е к с н ы х чисел п р и м е н я е т с я с л е д у ю щ а я формула: *
Глава
Полиморфизм
-
175
Деление всего в ы г л я д и т ф о р м у л а , по которой р а с с ч и т ы в а е т с я р е з у л ь т а т д е л е н и я од¬ ного к о м п л е к с н о г о числа на д р у г о е : +
Класс для представления комплексных чисел Для п р е д с т а в л е н и я к о м п л е к с н ы х
чисел
мы р а з р а б о т а л и
класс с
В первом в а р и а н т е этого класса мы п р е д у с м о т р е л и поля
и
именем
Complex.
предназначенные
с о о т в е т с т в е н н о для х р а н е н и я д е й с т в и т е л ь н о й и м н и м о й части к о м п л е к с н о г о числа, к о н с т р у к т о р , а т а к ж е два м е т о д а , A d d и S u b , для с л о ж е н и я и в ы ч и т а н и я к о м п л е к с н ы х чисел: class public public
double double
public
Complex ( d o u b l e r,
re im
public
double
i)
i
static
return
public
re;
Complex
x2)
Complex
Complex
x2)
new
static
return
Complex
new
-
-
Конструктор копирует значения своих параметров в соответствующие поля класса. Первый параметр задает д е й с т в и т е л ь н у ю часть комплексного числа, а второй — мнимую. С т а т и ч е с к и е м е т о д ы A d d и S u b в о з в р а щ а ю т н о в ы й о б ъ е к т класса C o m p l e x , соз¬ д а н н ы й при п о м о щ и о п е р а т о р а n e w . К о н с т р у к т о р а м этих о б ъ е к т о в п е р е д а ю т с я пара¬ метры, вычисленные в соответствии с правилами сложения и вычитания комплексных чисел. П р и в е д е м п р и м е р р а б о т ы с этим к л а с с о м : Complex C o m p l e x c2
new new C o m p l e x (4,
"c2
c2 В.
im)
Г В. Фролов. Язык С# Самоучитель
Complex
sum c2
Complex
sub
Sub
c2);
- c2 В этом ф р а г м е н т е кода м ы сначала создали два к о м п л е к с н ы х числа и 5 Д а л е е мы вывели эти числа на к о н с о л ь в виде 2 i и (4, 5 И на к о н е ц , мы по очереди вызвали м е т о д ы A d d и S u b , о т о б р а з и в на к о н с о л и р е з у л ь т а т в ы полнения операций и в ы ч и т а н и я к о м п л е к с н ы х чисел. О д н а к о было бы н а м н о г о у д о б н е е и с п о л ь з о в а т ь д л я с л о ж е н и я и в ы ч и т а н и я к о м чисел не м е т о д ы A d d и S u b , а п р и в ы ч н ы е о п е р а т о р ы и например: C o m p l e x sum Complex sub
cl cl
-
с2; c2;
Ч т о б ы и с п о л ь з о в а т ь такие о п е р а ц и и при р а б о т е не т о л ь к о с о б ы ч н ы м и , действи¬ т е л ь н ы м и , но и с к о м п л е к с н ы м и ч и с л а м и , нам н е о б х о д и м о их п е р е г р у з и т ь в классе
Перегрузка бинарных операторов Для перегрузки о п е р а т о р о в нам н у ж н о д о б а в и т ь в наш класс с т а т и ч е с к и е м е т о д ы спе¬ ц и а л ь н о г о вида, п р и ч е м этот вид зависит от т о г о , будет п е р е г р у ж а т ь с я б и н а р н ы й или унарный оператор. Сначала мы рассмотрим перегрузку бинарных операторов. Н и ж е м ы привели и с х о д н ы й т е к с т м е т о д а класса C o m p l e x , п е р е г р у ж а ю щ е г о опе¬ ратор Сложения: public
static
return
Complex
operator
(Complex
Complex
new
Как в и д и т е , он очень п о х о ж на о п и с а н н ы й в ы ш е метод A d d , в ы п о л н я ю щ и й ту же с а м у ю о п е р а ц и ю . О т л и ч и е з а к л ю ч а е т с я в т о м , что в качестве имени м е т о д а в м е с т о A d d м ы и с п о л ь з о в а л и к о н с т р у к ц и ю вида o p e r a t o r А н а л о г и ч н ы м образом перегружаются операторы вычитания, умножения и деления: public
static
r e t u r n new
public
static
return xl
Глава
Complex
operator
Complex ( x l
Complex
xl,
-
operator
-
(Complex
Complex x2
xl,
im)
Complex
new re - xl
Полиморфизм
im*x2
x2
im)
public
static
return
Complex
operator
Complex
new -
Д е й с т в и я в ы п о л н я ю т с я э т и м и о п е р а т о р а м и в с о о т в е т с т в и и с п р а в и л а м и вычисле¬ ний над к о м п л е к с н ы м и
ч и с л а м и , о п и с а н н ы м и ранее в р а з д е л е « К р а т к а я теория ком¬
плексных чисел». Д о б а в и в в к л а с с C o m p l e x п е р е ч и с л е н н ы е в ы ш е м е т о д ы , п е р е о п р е д е л я ю щ и е ос¬ н о в н ы е а р и ф м е т и ч е с к и е о п е р а ц и и , мы м о ж е м з а п и с ы в а т ь в ы р а ж е н и я с к о м п л е к с н ы м и ч и с л а м и более е с т е с т в е н н ы м о б р а з о м : Complex Complex
cl c2
Complex
sural
new new
( 1 , 2) 5)
cl
c2; c2
Complex
cl
-
c2; - c2
Complex mul
c2 Complex Console
c2; c2
Здесь мы с о з д а л и д в а к о м п л е к с н ы х числа и в ы в е л и на к о н с о л ь р е з у л ь т а т ы выпол¬ нения операций сложения, вычитания, умножения и деления.
Перегрузка унарных операторов У н а р н ы е о п е р а т о р ы , как и з в е с т н о , п о л у ч а ю т т о л ь к о один о п е р а н д . Х о т я о п е р а ц и и ин¬ к р е м е н т а и д е к р е м е н т а к о м п л е к с н ы х ч и с е л о б ы ч н о не и с п о л ь з у ю т с я , мы п е р е г р у з и м соответствующие операторы
и
для д е м о н с т р а ц и и п р и е м о в п е р е г р у з к и у н а р н ы х
операторов. П р и этом мы будем с ч и т а т ь , что для и н к р е м е н т а к о м п л е к с н о г о числа н у ж н о приба¬ вить е д и н и ц у к его д е й с т в и т е л ь н о й и м н и м о й части, а для д е к р е м е н т а — в ы ч е с т ь еди¬ ницу из д е й с т в и т е л ь н о й и м н и м о й ч а с т и . М е т о д ы для п е р е г р у з к и у н а р н ы х о п е р а ц и й п о л у ч а е т т о л ь к о один п а р а м е т р : public
static
r e t u r n new
Complex
operator
x)
1,
1);
В
Г. В. Фролов. Язык
Самоучитель
public
static
Complex
operator
r e t u r n new
-
1,
x) -
1);
В о т как м ы м о ж е м и с п о л ь з о в а т ь п е р е г р у ж е н н ы е т а к и м с п о с о б о м о п е р а ц и и инкре¬ мента и д е к р е м е н т а : C o m p l e x cl C o m p l e x c2 Complex
new C o m p l e x ( l , new
incr
+ + cl;
Complex Как в и д и т е , теперь мы м о ж е м в ы п о л н я т ь у н а р н ы е о п е р а ц и и с к о м п л е к с н ы м и чис¬ лами т а к и м же с п о с о б о м , каким они в ы п о л н я ю т с я и с о б ы ч н ы м и д е й с т в и т е л ь н ы м и числами.
Перегрузка операторов сравнения Для того ч т о б ы с р а в н и в а т ь о б ъ е к т ы , с о з д а н н ы е на базе р а з р а б о т а н н ы х вами к л а с с о в , н у ж н о п е р е г р у з и т ь все или н е к о т о р ы е о п е р а т о р ы с р а в н е н и я , т а к и е , как а т а к ж е м е т о д ы б а з о в о г о класса o b j e c t с и м е н а м и E q u a l s и GetHashCode. З а м е т и м , что если вы п е р е г р у ж а е т е о п е р а т о р р а в е н с т в а в м е с т е с ним н у ж но обязательно перегружать и оператор неравенства перегрузке операторов и нужно дополнительно перегружать операторы и В о т п р и м е р п е р е г р у з к и о п е р а т о р о в р а в е н с т в а и н е р а в е н с т в а д л я н а ш е г о класса public
static
return else return public
static
return else return
public
operator
Complex
x2)
Complex
x2)
true; false; bool
operator
!=
xl,
true; false;
override
return
(Complex
bool
true;
Глава 4. Полиморфизм
o)
public
override
return
int
0;
Как в и д и т е , м е т о д ы o p e r a t o r и operator п о л у ч а ю т два параметра (ссылки н а с р а в н и в а е м ы е о б ъ е к т ы ) в о з в р а щ а ю т значение t r u e , если дей¬ с т в и т е л ь н ы е и м н и м ы е части с р а в н и в а е м ы х к о м п л е к с н ы х чисел с о о т в е т с т в е н н о сов¬ п а д а ю т или не с о в п а д а ю т . Здесь все п о н я т н о , так как в н е ш н е эти м е т о д ы очень похо¬ жи на т о л ь к о что р а с с м о т р е н н ы е м е т о д ы п е р е г р у ж е н н ы х б и н а р н ы х о п е р а т о р о в . Н о н е о б х о д и м о с т ь перегрузки м е т о д о в E q u a l s и G e t H a s h C o d e т р е б у е т допол¬ нительного пояснения. Мы уже у п о м и н а л и , что в я з ы к е п р о г р а м м и р о в а н и я С# все классы н а с л е д у ю т с я от о д н о г о б а з о в о г о класса O b j e c t . В этом классе о б ъ я в л е н о н е с к о л ь к о м е т о д о в , кото¬ рые иногда п р и х о д и т с я п е р е о п р е д е л я т ь в п р о и з в о д н ы х классах. В частности, метод E q u a l s позволяет сравнить два объекта, а метод G e t H a s h C o d e нужен для получения так называемого хеш-кода объекта. Х е ш - к о д однозначно идентифи¬ цирует каждый объект класса и применяется для быстрого поиска объектов по ключу. М е т о д ы E q u a l s и G e t H a s h C o d e в з а и м о с в я з а н ы в том с м ы с л е , что д в у м объек¬ т а м , к о т о р ы е м е т о д E q u a l s с ч и т а е т о д и н а к о в ы м и , м е т о д G e t H a s h C o d e д о л ж е н воз вращать о д и н а к о в ы е з н а ч е н и я Н а ш а р е а л и з а ц и я м е т о д а E q u a l s с ч и т а е т р а в н ы м и л ю б ы е п е р е д а в а е м ы е е й объек¬ т ы , в о з в р а щ а я всегда з н а ч е н и е t r u e . Что ж е касается м е т о д а G e t H a s h C o d e , т о о н всегда в о з в р а щ а е т о д и н а к о в о е з н а ч е н и е 0 . Для того ч т о б ы с р а в н и в а т ь к о м п л е к с н ы е числа класса C o m p l e x , такая р е а л и з а ц и я м е т о д о в E q u a l s и G e t H a s h C o d e вполне пригодна.
Пример программы В л и с т и н г е 4.5 мы п р и в е л и и с х о д н ы й текст п р о г р а м м ы , в которой для работы с ком¬ п л е к с н ы м и ч и с л а м и п р и м е н я ю т с я как о б ы ч н ы е м е т о д ы , о п р е д е л е н н ы е в классе C o m p l e x , так и п е р е г р у ж е н н ы е о п е р а т о р ы . Все п р и е м ы , п р и м е н е н н ы е нами в этой п р о г р а м м е , у ж е были о п и с а н ы ранее, по¬ этому мы о с т а в л я е м эту п р о г р а м м у вам для с а м о с т о я т е л ь н о г о и з у ч е н и я . Листинг
4.5.
Файл
using System; n a m e s p a c e ComplexNum class
Complex
public public
double double
public
Complex (double
re im
re; im; r,
double
i)
r i А В.
Г. В.
Язык С# Самоучитель
public
static
r e t u r n new
public
static
return
public
public
public
Complex ( x l . r e
xl.im
Complex
xl,
operator
Complex
operator
new
static
static
static
return
x2)
-
Complex
(Complex x l ,
Complex
xl,
Complex
Complex
x2)
Complex
x2)
-
operator
x)
1, Complex
operator
r e t u r n new public
Complex
-
r e t u r n new public
Complex
new
static
return
Add ( C o m p l e x
new
static
return
Complex
Complex
x)
1,
-
operator
1);
* (Complex x l ,
new -
xl.re*x2.im
public
static
return
x2
Complex
im)
operator
Complex
new -
public
static
return else return
Глава 4. Полиморфизм
bool
true; false;
operator
(Complex
Complex
x2)
public
static
bool
operator
(Complex
Complex
im return else return
public
true; false;
override
o)
return public
override
return
static
int
0;
void
args)
Complex Complex
cl c2
Complex
sum
new
c2); c2
-
Complex sub Complex
suml
c2
cl c2
Complex subl Console
cl
-
c2; -
c2
C o m p l e x mul c2 Complex div Console Complex Console
cl c2
incr
Complex Console A,
Г.
Фролов. Язык
Самоучитель
if(cl
c2)
else
Класс Ранее мы у ж е н е о д н о к р а т н о у п о м и н а л и класс от к о т о р о г о автома¬ т и ч е с к и н а с л е д у ю т с я все о б ъ е к т ы в п р о г р а м м а х С#. К а к вы у в и д и т е в д а л ь н е й ш е м , на¬ л и ч и е е д и н о г о к о р н я в д е р е в е н а с л е д о в а н и я классов С# дает н е м а л ы е п р е и м у щ е с т в а . Н а п р и м е р , о б ъ е к т ы л ю б о г о класса м о ж н о х р а н и т ь в г о т о в ы х к о н т е й н е р а х , предостав¬ л я е м ы х б и б л и о т е к о й классов Microsoft Framework. В п р е д ы д у щ е м р а з д е л е мы р а с с к а з ы в а л и в а м о м е т о д а х E q u a l s и п е р в ы й из к о т о р ы х и м е е т о т н о ш е н и е к с р а в н е н и ю о б ъ е к т о в , а второй п р е д н а з н а ч е н для п о л у ч е н и я н а з ы в а е м о г о х е ш - к о д а , о д н о з н а ч н о и д е н т и ф и ц и р у ю щ е г о объект. П о м и м о этих м е т о д о в , для р а з р а б о т ч и к о в п р о г р а м м С # м о г у т п р е д с т а в л я т ь и н т е р е с ToString, и М е т о д T o S t r i n g позволяет получить имя объекта. Переопределяя это и м я в произ¬ водных классах, можно использовать перегруженную версию этого метода для предостав¬ ления расширенной информации об объекте, не ограничиваясь одним только именем. Что касается м е т о д а G e t T y p e , то с его п о м о щ ь ю м о ж н о п о л у ч и т ь д о п о л н и т е л ь ¬ н у ю и н ф о р м а ц и ю о типе объекта. Э т о т м е т о д мы будем у п о м и н а т ь в р а з д е л е нашей книги, посвященной атрибутам. И н а к о н е ц , п е р е о п р е д е л е н и е метода F i n a l i z e н у ж н о для о с в о б о ж д е н и я какихл и б о р е с у р с о в ( ф а й л о в , с о е д и н е н и й с базами д а н н ы х и т. п.) перед т е м , как о б ъ е к т бу¬ дет у н и ч т о ж е н с и с т е м о й сборки мусора. В л и с т и н г е 4.6 мы п р и в е л и п р и м е р п р о г р а м м ы , д е м о н с т р и р у ю щ е й и с п о л ь з о в а н и е метода T o S t r i n g . Листинг
4.6.
Файл
using System; namespace SysObj class
Point
public public
double double
public
P o i n t (double x,
xPos
Глава
x;
Полиморфизм
xPos; yPos; double
y)
public
void
x,
double
y)
точки yPos
x,
Point
class public
PointSmart (double x,
public
override
double
y)
base
(x,
y)
string
return
class
в
X,у;
at
xPos,
yPos)
SysObjApp
static
void
int
args) 38; toString:
mylnt,
ToString
toString:
Point
pt
new
Point(l,
2); toString:
pt.xPos, PointSmart
pt.yPos, pts
new
PointSmart toString:
П е р в о е , что
наша п р о г р а м м а ,
это с о з д а с т п е р е м е н н у ю m y l n t типа
int
и вызывает для нес м е т о д
В Фролов, Г. В.
Язык
Самоучитель
38; toString: М ы у ж е г о в о р и л и в а м , что такие типы д а н н ы х , как i n t , н а самом деле я в л я ю т с я классами С # , поэтому в записи о String нет н и ч е г о н е о б ы ч н о г о . Что же будет п о к а з а н о на консоли в
р а б о т ы этих строк п р о г р а м м ы ?
Там появится текстовая строка 3 8 , которая п р е д с т а в л я е т объект m y l n t : mylnt
38,
toString:
38
А н а л о г и ч н о н а ш а п р о г р а м м а в ы з ы в а е т метод T o S t r i n g для п е р е м е н н о й типа
bool:
bool
myBool
true; toString:
myBool,
ToString
На к о н с о л и будет о т о б р а ж е н а с л е д у ю щ а я строка: myBool
True,
toString:
True
Как в и д и т е , т е к с т о в о е п р е д с т а в л е н и е о б ъ е к т о в п р о с т е й ш и х т и п о в д а н н ы х просто з н а ч е н и е , х р а н я щ е е с я в этих о б ъ е к т а х . А как в ы г л я д и т т е к с т о в о е п р е д с т а в л е н и е класса Point?
это
Для того чтобы это узнать, в нашей программе предусмотрены следующие строки: Point
pt
new
(1, toString:
pt.xPos,
pt.yPos,
ToString
З а п у с т и в п р о г р а м м у н а в ы п о л н е н и е , м о ж н о у б е д и т ь с я , что для класса P o i n t ме¬ тод T o S t r i n g в ы в о д и т не к о о р д и н а т ы т о ч к и (как это м о ж н о было бы п о д у м а т ь ) , а на¬ звание класса и п р о с т р а н с т в а и м е н , в к о т о р о м этот класс о п р е д е л е н : pt
(1,
toString:
В классе P o i n t S m a r t м ы п е р е о п р е д е л и л и метод T o S t r i n g т а к и м о б р а з о м , что¬ бы он о т о б р а ж а л на к о н с о л и и м е н н о к о о р д и н а т ы т о ч к и , а не н а з в а н и е класса: public
override
string
return
at
yPos);
Наш в а р и а н т метода T o S t r i n g в о з в р а щ а е т строку с к о о р д и н а т а м и т о ч к и , сфор¬ м и р о в а н н у ю при
п о м о щ и класса
среды в ы п о л н е н и я Microsoft
F o r m a t , входящего в библиотеку классов Framework.
Вот как м ы в ы з ы в а е м п е р е о п р е д е л е н н ы й м е т о д T o S t r i n g в н а ш е й п р о г р а м м е : PointSmart
pts
new
PointSmart toString:
Глава 4. Полиморфизм
Как и с л е д о в а л о о ж и д а т ь , новый в а р и а н т метода T o S t r i n g в ы в о д и т н а экран то, что нам н у ж н о , а именно к о о р д и н а т ы т о ч к и : pts
(3,
4),
toString:
Point
at
(3,
4)
П е р е о п р е д е л я я в с о з д а в а е м ы х к л а с с а х метод T o S t r i n g , вы м о ж е т е наделить его ф у н к ц и я м и , п о л е з н ы м и , н а п р и м е р , для о т л а д к и или для о т о б р а ж е н и я т е к у щ е г о со¬ стояния для п о л у ч е н и я л ю б о й и н ф о р м а ц и и о б о б ъ е к т е , к о т о р у ю м о ж н о п р е д с т а в и т ь в виде текста.
А.
Фролов. Г.
Фролов. Язык
Самоучитель
Глава 5. Преобразование типов объектов К а к мы у ж е г о в о р и л и , п р о г р а м м ы С# р а б о т а ю т с л о к а л ь н ы м и п е р е м е н н ы м и и п о л я м и классов р а з л и ч н ы х т и п о в . Это м о г у т быть с т а н д а р т н ы е т и п ы С # ( ч и с л о в ы е , с т р о к о в ы е , л о г и ч е с к и е и т. д.), а т а к ж е т и п ы д а н н ы х , о п р е д е л е н н ы х при п о м о щ и классов (напри¬ м е р , р а с с м о т р е н н ы е в п р е д ы д у щ е й главе к о м п л е к с н ы е ч и с л а ) . П р и с о с т а в л е н и и в ы р а ж е н и й , в к л ю ч а ю щ и х в себя п е р е м е н н ы е и поля р а з л и ч н ы х типов,
выполняется
м о ж е т быть явной,
операция
преобразования
типов
(приведения
типов). Эта
операция
когда п р о г р а м м и с т сам у к а з ы в а е т , к к а к о м у типу н у ж н о п р и в е с т и
д а н н ы й тип, а т а к ж е неявной.
В п о с л е д н е м случае к о м п и л я т о р в ы п о л н я е т преобразо¬
вание т и п о в а в т о м а т и ч е с к и . П р о г р а м м и с т м о ж е т также о п р е д е л и т ь с о б с т в е н н ы й способ п р е о б р а з о в а н и я т и п о в . Такая в о з м о ж н о с т ь н у ж н а при и с п о л ь з о в а н и и в в ы р а ж е н и я х т и п о в д а н н ы х , с о з д а н н ы х п р о г р а м м и с т о м с п о м о щ ь ю классов.
преобразование числовых типов С о с т а в л я я в ы р а ж е н и я с ч и с л а м и , в п р е д ы д у щ и х главах мы и с п о л ь з о в а л и т а к у ю воз¬ м о ж н о с т ь я з ы к а С # , как явное и н е я в н о е п р е о б р а з о в а н и е т и п о в . В п р о ц е с с е т а к о г о п р е о б р а з о в а н и я о б ъ е к т ы о д н о г о ч и с л о в о г о т и п а а в т о м а т и ч е с к и п р и в о д и л и с ь к объек¬ там д р у г о г о типа (также ч и с л о в о г о ) . Н а п р и м е р , р а с с м о т р и м с л е д у ю щ у ю п р о г р а м м у ( л и с т и н г 5.1). 5.1.
Листинг
Файл
using System; namespace class static
void
int intNuber double double r e s u l t
args) 5 2.5; doubleNumber
intNuber; сложения 5 2.5
мы объявили две
переменные
и
вая из к о т о р ы х п р е д н а з н а ч е н а для х р а н е н и я целых ч и с е л , а вторая
пер для х р а н е н и я чи¬
сел с п л а в а ю щ е й т о ч к о й . Н а ш а п р о г р а м м а п р е д п р и н и м а е т п о п ы т к у с л о ж е н и я чисел р а з н о г о типа с з а п и с ь ю результата в п е р е м е н н у ю т и п а d o u b l e : double
result
intNuber;
В п р о ц е с с е к о м п и л я ц и и п р о г р а м м ы в ы п о л н я е т с я а в т о м а т и ч е с к о е н е я в н о е преобра¬ з о в а н и е типа п е р е м е н н о й i n t N u b e r в тип d o u b l e , в р е з у л ь т а т е чего с л о ж е н и е вы¬ полняется п р а в и л ь н о . В о т что н а ш а п р о г р а м м а в ы в о д и т н а к о н с о л ь : Результат
сложения
5
2.5
З а м е т и м , что а в т о м а т и ч е с к о е п р е о б р а з о в а н и е ч и с л о в ы х т и п о в в о з м о ж н о не всегда. Ч т о б ы к о м п и л я т о р смог его в ы п о л н и т ь , в р е з у л ь т а т е п р е о б р а з о в а н и я не д о л ж н ы те¬ р я т ь с я з н а ч и м ы е ц и ф р ы р е з у л ь т а т а . П о э т о м у если
исходные преобразуемые данные
з а н и м а ю т в п а м я т и м е н ь ш е места, чем д а н н ы е результата п р е о б р а з о в а н и я , то автома¬ т и ч е с к о е п р е о б р а з о в а н и е в о з м о ж н о , а если б о л ь ш е , то нет.
Числа без знака С х е м ы в о з м о ж н ы х н е я в н ы х п р е о б р а з о в а н и й чисел без знака и чисел со з н а к о м отли чаются
друг
от д р у г а .
Сначала
мы
рассмотрим
преобразования
чисел
без
знака.
На рис. 5.1 мы показали схему д о п у с т и м ы х п р е о б р а з о в а н и й для таких чисел.
float
J
j
Неявные
decimal
преобразования
J чисел
без знака
Как в и д и т е , з н а ч е н и я типа b y t e могут быть п р и в е д е н ы к типу uint,
а также к
short, ushort,
d e c i m a l . Что ж е касается а в т о м а т и ч е с к о г о приве¬
д е н и я т и п а u i n t к типу b y t е или u s h o r t , т о оно н е в о з м о ж н о .
А В Фролов, Г. В Фролов Язык
Числа со знаком н е я в н ы е п р е о б р а з о в а н и я для чисел со знаком п о к а з а н ы на рис. 5.2.
Рис.
5.2.
Неявные
преобразования
чисел
со знаком
Как видите, транслятор не может автоматически преобразовать числа со знаком в ч и с л а без з н а к а . Т а к и м о б р а з о м , с л е д у ю щ и й ф р а г м е н т кода т р а н с л и р о в а т ь с я не будет: int i u i n t ui uint
5; 6 ui
i;
Текстовые символы char Как
мы
уже говорили,
текстовые
символы
c h a r языка С#
хранятся
в
кодировке
U N I C O D E , п р и ч е м для к а ж д о г о с и м в о л а т р е б у е т с я 2 байта п а м я т и . П о э т о м у тип c h a r м о ж е т быть
автоматически преобразован в типы u s h o r t
в д р у г и е т и п ы в с о о т в е т с т в и и с рис. 5.1 и 5.2.
Глава 5. Преобразование
объектов
и
i n t (рис.
5.3), а т а к ж е
Числа с плавающей точкой П р о щ е всего с х е м а п р е о б р а з о в а н и й в ы г л я д и т для типа
для
п р е д с т а в л е н и я чисел с п л а в а ю щ е й т о ч к о й (рис. 5.4).
Рис. Тип д а н н ы х
5.4.
Преобразования
типа
float
быть а в т о м а т и ч е с к и п р и в е д е н т о л ь к о к типу
Явное преобразование числовых типов В некоторых случаях необходимо использовать явное преобразование типов. Если, н а п р и м е р , у вас есть и с х о д н а я п е р е м е н н а я т и п а u l o n g , но вы т о ч н о з н а е т е , что в ней х р а н я т с я з н а ч е н и я , н е п р е в о с х о д я щ и е 2 5 5 , т о м о ж н о в ы п о л н и т ь с л е д у ю щ е е преобра¬ зование: ulongNuber resultByte
11;
О б р а т и т е в н и м а н и е , что п е р е д и м е н е м п е р е м е н н о й u l o n g N u b e r м ы у к а з а л и в к р у г л ы х с к о б к а х т и п , к к о т о р о м у н у ж н о п р и в е с т и и с х о д н ы й тип д а н н ы х . Это так на¬ зываемый оператор приведения типа. О п е р а т о р п р и в е д е н и я типа з а с т а в л я е т к о м п и л я т о р в ы п о л н и т ь з а д а н н о е преобразо¬ вание т и п о в , д а ж е если при э т о м и с х о д н о е з н а ч е н и е числа о к а ж е т с я и с к а ж е н н ы м . Р а с с м о т р и м п р и м е р п р о г р а м м ы ( л и с т и н г 5.2), д е м о н с т р и р у ю щ е й т а к о е и с к а ж е н и е . Листинг 5.2.
Файл
ch05\Cast\CastApp.cs
using System; namespace Cast class
CastApp
static
void
args)
ulongNuber byte r e s u l t B y t e ushort resultUshort uint r e s u l t U i n t число
190
типа
В Фролов, Г.
ulong
Фролов
Самоучитель
типа
byte\t=
типа типа
Эта п р о г р а м м а п о с л е д о в а т е л ь н о п р и с в а и в а е т з н а ч е н и е 0 x 1 1 2 2 3 3 4 4 5 5 , х р а н я щ е е ся
в
исходной
переменной
типа
ulong,
переменным
типа
byte,
u s h o r t и u i n t . Д а л е е п р о г р а м м а о т о б р а ж а е т и с х о д н о е з н а ч е н и е и р е з у л ь т а т выпол¬ нения о п е р а ц и й п р и с в а и в а н и я в ш е с т н а д ц а т е р и ч н о м виде: Исходное
число
типа
ulong
Р е з у л ь т а т типа b y t e Р е з у л ь т а т типа u s h o r t Р е з у л ь т а т типа u i n t
1122334455
55 4455 22334455
Как в и д и т е , л ю б о е из п р и м е н е н н ы х в п р о г р а м м е п р е о б р а з о в а н и й т и п о в в ы з ы в а е т и с к а ж е н и е и с х о д н о г о з н а ч е н и я . И м е н н о этого и с л е д о в а л о о ж и д а т ь , так как для ре¬ зультата о т в о д и т с я м е н ь ш е места, чем з а н и м а е т и с х о д н о е ч и с л о . П р и этом с т а р ш и е разряды преобразуемого значения попросту отбрасываются. Г л а в н ы й в ы в о д о т с ю д а з а к л ю ч а е т с я в т о м , что и с п о л ь з о в а т ь я в н о е п р е о б р а з о в а н и е ч и с л о в ы х типов н у ж н о с о с т о р о ж н о с т ь ю , так как оно м о ж е т п р и в е с т и к и с к а ж е н и ю исходного значения.
Проверка преобразования числовых типов Для того ч т о б ы з а с т р а х о в а т ь с я от о ш и б о к , в о з н и к а ю щ и х в п р о ц е с с е п р и в е д е н и я т и п о в , в языке С# предусмотрена специальная конструкция c h e c k e d . В о т п р и м е р ее и с п о л ь з о в а н и я : ulong ulongNuber ushort checked resultUshort
В блок c h e c k e d п о м е щ а ю т с я о п е р а т о р ы с п р и в е д е н и е м типа, при в ы п о л н е н и и ко¬ торых м о ж е т п р о и с х о д и т ь потеря с т а р ш и х р а з р я д о в и с х о д н о г о з н а ч е н и я . П р о в е р к а в ы п о л н я е т с я во время р а б о т ы п р о г р а м м ы , так как на этапе к о м п и л я ц и и з н а ч е н и е и с х о д н о й п е р е м е н н о й м о ж е т быть н е и з в е с т н о . Глава
Преобразование типов объектов
191
В п р и в е д е н н о м выше ф р а г м е н т е кода при в ы п о л н е н и и п р и с в а и в а н и я потеря с т а р ш и х р а з р я д о в и с х о д н о г о з н а ч е н и я . Так как эта н а х о д и т с я в блоке c h e c k e d , в результате возникнет исключение Об и с к л ю ч е н и я х мы р а с с к а ж е м п о з ж е , в гл. 9. П о к а вам д о с т а т о ч н о знать, что про г р а м м а м о ж е т « п е р е х в а т ы в а т ь » и с к л ю ч е н и я и о б р а б а т ы в а т ь о ш и б о ч н ы е ситуации Ес¬ ли же о б р а б о т к а и с к л ю ч е н и я не п р е д у с м о т р е н а , то п р о г р а м м а з а в е р ш и т с в о ю работу а в а р и й н о и в ы в е д е т с о о т в е т с т в у ю щ е е с о о б щ е н и е . В нашем случае это будет сообще¬ ние, п р и в е д е н н о е н и ж е : exception Additional
information:
of
type
occurred
Arithmetic
operation
resulted
in
an
in
overflow.
Преобразования типов и классы Мы уже г о в о р и л и , что в я з ы к е С# все д а н н ы е (даже числа и с и м в о л ы ) п р е д с т а в л я ю т с я как объекты соответствующих классов, унаследованных от корневого класса У п р а в л е н и е т и п а м и в Microsoft Framework универ система типов Common Type System ( C T S ) , о п р е д е л я ю щ а я типы и вила их п р и в е д е н и я . Н а л и ч и е C T S о б е с п е ч и в а е т и с п о л ь з о в а н и е единой системы типов в р а м к а х л ю б ы х я з ы к о в п р о г р а м м и р о в а н и я , р а з р а б о т а н н ы х для п л а т ф о р м ы Microsoft Framework. Это, в частности, позволяет создавать отдельные компоненты и объекты создаваемой программы на разных языках программирования.
Псевдонимы типов данных В я з ы к е С# все ч и с л о в ы е т и п ы д а н н ы х п р е д с т а в л я ю т собой к л а с с ы , о б ъ я в л е н н ы е в п р о с т р а н с т в е имен S y s t e m . Для п р о с т о т ы к о м п и л я т о р С # д о п у с к а е т и с п о л ь з о в а н и е в м е с т о имен этих у ж е з н а к о м ы х вам п с е в д о н и м о в . В табл. 5.1 мы привели п с е в д о н и м ы ч и с л о в ы х т и п о в , у п о м и н а в ш и х с я в гл. 1. Таблица
Псевдонимы
для
имен
классов
Псевдоним byte
Имя System
ushort uint
числовых
типов
данных
класса
Byte Uintl6
System
ulong
Uint32 Uint64 Sbyte
sby te short
System
Intl6
int
System
Int32
long
System
Int64 Single Double
double decimal
System
Decimal
А В Фролов, Г. В Фролов Язык
Самоучитель
В С# предусмотрен также набор псевдонимов для классов для логических, строковых и символьных данных, а также для базового класса (табл. 5.2). Таблица
5.2.
Псевдонимы
для
имен
классов
прочих типов
Псевдоним
Имя
bool
Boolean
char
Char
string
System
object
Sys
данных
класса
В своих программах вы можете использовать вместо псевдонимов имена соответ ствующих классов, если считаете, что так программа будет понятнее. Однако тем, кто уже создавал программы на других языках программирования, псевдонимы С# скорее всего будут у д о б н е е . В листинге 5.3 мы показали немного измененный вариант п р е д ы д у щ е й программы, д е м о н с т р и р у ю щ е й явное преобразование числовых типов данных. Листинг
5.3.
Файл
using System; namespace class static
void
UInt32
args)
ulongNuber resultByte resultUshort resultUint ч
типа
ulong
ulongNuber)
resultUint)
типа
byte\t=
типа
ushort\t=
типа
uint\t=
Обратите внимание, что здесь мы заменили псевдонимы именами соответствую¬ щих классов. В следующих двух строках программы мы не указали пространство имен S y s t e m , так как его просмотр задан в начале программы оператором u s i n g : Глава 5. Преобразование типов объектов 7
Язык
Самоучитель
UInt32 К о г д а н а з в а н и я классов и с п о л ь з о в а т ь у д о б н е е , чем п с е в д о н и м ы ? Н а п р и м е р , когда вам н у ж н о следить за р а з р я д н о с т ь ю п е р е м е н н ы х . Н а п р и м е р , с м о ж е т е ли вы сразу в с п о м н и т ь , какова р а з р я д н о с т ь п е р е м е н н о й типа (т. е. с к о л ь к о р а з р я д о в д а н н ы х з а н и м а ю т з н а ч е н и я этого т и п а ) ? Н а з в а н и е класса S y s t e m . I n t l 6 сразу д а е т ответ на этот д а н н ы е зани м а ю т в п а м я т и 16 р а з р я д о в . Т а к и м о б р а з о м , п р и м е н е н и е н а з в а н и й к л а с с о в , п е р е ч и с л е н н ы х в табл. 5.1 и 5.2, вместо с о о т в е т с т в у ю щ и х п с е в д о н и м о в п о з в о л я е т в н е к о т о р ы х случаях у п р о с т и т ь для п р о г р а м м и с т а к о н т р о л ь и с п о л ь з о в а н и я т и п о в д а н н ы х . Для п о в ы ш е н и я п р о и з в о д и т е л ь н о с т и при р а б о т е с о б ъ е к т а м и , п р е д с т а в л я ю щ и м и числа и с и м в о л ы , в языке С# п р и м е н я е т с я т е х н о л о г и я упаковки ( b o x i n g ) . Эта т е х н о л о гия п р е о б р а з о в а н и е о б ъ е к т о в к л а с с о в в о б ы ч н ы е пере м е н н ы е , р а з м е щ а е м ы е в о п е р а т и в н о й п а м я т и к о м п ь ю т е р а , когда это д о п у с т и м о . Об¬ р а т н о е п р е о б р а з о в а н и е т а к ж е в ы п о л н я е т с я а в т о м а т и ч е с к и . Д л я п р о г р а м м и с т а все эти д е й с т в и я а б с о л ю т н о п р о з р а ч н ы , п о э т о м у у п а к о в к а н е в ы з ы в а е т д о п о л н и т е л ь н ы х с л о ж н о с т е й при с о з д а н и и п р о г р а м м .
Приведение производных и базовых классов Р а с с к а з ы в а я о п о л и м о р ф и з м е в п р е д ы д у щ е й г л а в е , в о д н о м из п р и м е р о в п р о г р а м м (см. л и с т и н г 4.4) м ы с о з д а в а л и б а з о в ы й класс S h a p e
с абстрактным методом Draw,
а затем п о л у ч а л и о т него п р о и з в о д н ы е классы P o i n t и R e c t a n g l e : abstract
class
abstract
class
public
Point
public
Shape
void
x,
int
x,
int
Shape
override
void
точки в xPos xPos
class
Rectangle
public
x,
у)
x; y;
Shape
override
Console xPos yPos
void
Draw(int
x,
int
y)
прямоугольника в
x,
x;
А. В Фролов, Г. В Фролов. Язык
Самоучитель
В о о р у ж и в ш и с ь этими классами, в методе M a i n мы проделали следующие операции: allShapes allShapesCO]
new
new P o i n t ( l , 1, 3,
allShapes[3]
new
4); in
Обратите
внимание,
что
здесь
allShapes) мы
объявили
массив,
созданный
из
объектов
класса S h a p e , н о записали в него ссылки н а о б ъ е к т ы п р о и з в о д н ы х к л а с с о в P o i n t и Rectangle. А н а л о г и ч н ы е д е й с т в и я в ы п о л н я л и с ь и в п р о г р а м м е , и с х о д н ы й т е к с т к о т о р о й был п р и в е д е н в л и с т и н г е 4.3: Shape
pt
Shape
rect
new
new
Point
25);
Rectangle(l,
4,
10,
Здесь в л о к а л ь н ы е п е р е м е н н ы е pt и r e c t , с о з д а н н ы е на базе класса S h a p e , т о ж е з а п и с ы в а ю т с я ссылки н а о б ъ е к т ы п р о и з в о д н ы х классов P o i n t и R e c t a n g l e . Т а к и м о б р а з о м , в процессе п р и с в а и в а н и я в ы п о л н я е т с я н е я в н о е п р е о б р а з о в а н и е т и п а ссыл¬ ки — тип с с ы л о к на п р о и з в о д н ы е классы п р и в о д и т с я к типу ссылки на б а з о в ы й к л а с с . Такое п р и в е д е н и е т и п о в н а з ы в а е т с я восходящим (upcast). И с п о л ь з у я п о л и м о р ф и з м , мы м о ж е м вызвать м е т о д ы п р о и з в о д н ы х к л а с с о в при по¬ мощи в и р т у а л ь н ы х или а б с т р а к т н ы х м е т о д о в базового класса. Что же касается нисходящего (downcast) п р и в е д е н и я с с ы л о к на б а з о в ы й к л а с с к ти пу ссылки на п р о и з в о д н ы й к л а с с , то оно н е д о п у с т и м о . Если мы в этом случае приме¬ ним явное п р и в е д е н и е т и п о в , к о м п и л я т о р не в ы в е д е т с о о б щ е н и е об о ш и б к е , но в про¬ цессе р а б о т ы п р о г р а м м ы м о ж е т в о з н и к н у т ь и с к л ю ч е н и е .
Операторы is и as Для того ч т о б ы в п р о ц е с с е п р е о б р а з о в а н и я т и п о в не в о з н и к л о и с к л ю ч е н и е , п е р е д вы¬ п о л н е н и е м этой о п е р а ц и и следует п р о в е р и т ь ее д о п у с т и м о с т ь . Для п р о в е р к и возмож¬ ности в ы п о л н е н и я п р е о б р а з о в а н и я т и п о в во в р е м я р а б о т ы п р о г р а м м ы в я з ы к е С# пре¬ д у с м о т р е н ы о п е р а т о р ы is и a s . О п е р а т о р a s в ы п о л н я е т я в н о е п р е о б р а з о в а н и е т и п о в . Если такое п р е о б р а з о в а н и е з а к о н ч и л о с ь н е у д а ч н о , в м е с т о ссылки на о б ъ е к т о п е р а т о р as в о з в р а щ а е т пустое зна¬ чение n u l l . Что ж е касается о п е р а т о р а i s , т о о н в о з в р а щ а е т t r u e или от т о г о , м о ж н о ли в ы п о л н и т ь п р е о б р а з о в а н и е или нет. Глава 5 Преобразование типов объектов
f a l s e в зависимости
195
П р и м е н е н и е обоих этих о п е р а т о р о в д е м о н с т р и р у е т с я в п р о г р а м м е , и с х о д н ы й текст которой Листинг
в листинге 5 . 4 . 5.4.
using System; namespace class
Point
public public
int int
x;
public x У
class
xO,
yO)
x,
i n t y,
xO
ColorPoint
public
int
Point
color;
public
class
int
int
c)
base(x,
y)
AsIsApp
static
void
Point pt ColorPoint
args) new cpt
Point(l, new
Point xPoint cpt; ColorPoint xColorPoint
2);
pt
2,
as
3);
ColorPoint; xPoint.x,
xPoint.y);
null)
else
А В Фролов. Г В. Фролов.
СМ. Самоучитель
new new P o i n t ( 3 , new
1
pointArray
(object i f (obj
else
is
if(obj
obj
in
pointArray)
ColorPoint)
is
В этой п р о г р а м м е м ы о п р е д е л и л и базовый класс P o i n t , п р е д с т а в л я ю щ и й точки на п л о с к о с т и , и п р о и з в о д н ы й от него класс ц в е т н ы х т о ч е к C o l o r P o i n t . По сравне¬ н и ю с б а з о в ы м классом п р о и з в о д н ы й класс C o l o r P o i n t д о п о л н е н полем c o l o r для х р а н е н и я з н а ч е н и я цвета. Все самое и н т е р е с н о е в этой п р о г р а м м е д е л а е т м е т о д M a i n . П р е ж д е всего он создает два о б ъ е к т а pt и c p t классов P o i n t и
C o l o r P o i n t со¬
ответственно: P o i n t pt ColorPoint
new cpt
new
2,
м ы з а п и с ы в а е м ссылку н а объект c p t в п е р е м е н н у ю x P o i n t , и м е ю щ у ю тип базового класса P o i n t : Point
xPoint
cpt;
Так как о п е р а ц и я п р и в е д е н и я ссылки к типу б а з о в о г о класса в ы п о л н я е т с я автома¬ т и ч е с к и , эта строка будет всегда в ы п о л н е н а без о ш и б о к . Далее н а ш а п р о г р а м м а смо¬ жет о т о б р а з и т ь к о о р д и н а т ы точки на к о н с о л и : xPoint.x, x P o i n t . y ) ;
Глава 5. Преобразование типов объектов
Но мы выполнить прямо противоположную операцию приведения типов з а п и с а т ь в п е р е м е н н у ю x C o l o r P o i n t с т и п о м д о ч е р н е г о класса ссылку на о б ъ е к т pt б а з о в о г о класса: ColorPoint Если появится что т а к о е записано
xColorPoint
pt
as
ColorPoint;
в н и м а н и е , что здесь м ы п р и м е н и л и о п е р а т о р a s . п р е о б р а з о в а н и е будет в ы п о л н е н о у с п е ш н о , в п е р е м е н н о й x C o l o r P o i n t н у ж н а я нам ссылка. Если же в п р о ц е с с е р а б о т ы п р о г р а м м ы в ы я с н и т с я , п р е о б р а з о в а н и е в ы п о л н и т ь н е в о з м о ж н о , в п е р е м е н н у ю x C o l o r P o i n t будет значение n u l l .
П е р е д тем как п ы т а т ь с я и с п о л ь з о в а т ь с о д е р ж и м о е п е р е м е н н о й x C o l o r P o i n t , его н е о б х о д и м о п р о в е р и т ь . Если в этой п е р е м е н н о й н а х о д и т с я п у с т о е з н а ч е н и е n u l l , на¬ ша п р о г р а м м а не будет о т о б р а ж а т ь к о о р д и н а т ы и цвет т о ч к и на к о н с о л и , а выведет туда с о о т в е т с т в у ю щ е е с о о б щ е н и е : if (xColorPoint
null)
Запуская н а ш у п р о г р а м м у для п р о в е р к и , вы у в и д и т е , что о п и с а н н о е в ы ш е преобра¬ з о в а н и е в ы п о л н и т ь так и не у д а л о с ь . В э т о м , о д н а к о , нет ничего у д и в и т е л ь н о г о . В с а м о м д е л е , мы создали о б ы ч н у ю , не¬ ц в е т н у ю точку pt н п ы т а е м с я з а п и с а т ь ссылку на нее в п е р е м е н н у ю с типом C o l o r P o i n t , п р е д п о л а г а ю щ и м д о п о л н и т е л ь н о х р а н е н и е и н ф о р м а ц и и о цвете. Если бы нам у д а л о с ь создать « ц в е т н у ю » ссылку на « ч е р н о - б е л ы й » о б ъ е к т , то по¬ п ы т к а п р и м е н е н и я этой с с ы л к и , н а п р и м е р , для п о л у ч е н и я и н ф о р м а ц и и о цвете т о ч к и , з а к о н ч и л а с ь бы н е у д а ч е й . Д е л о в т о м , что в и с х о д н о м о б ъ е к т е класса P o i n t нет ин¬ формации о цвете! Далее наша программа демонстрирует использование оператора i s . Мы создаем м а с с и в базового класса P o i n t , с о с т о я щ и й из д в у х я ч е е к , а затем запи¬ сываем в него с с ы л к и на о б ъ е к т ы базового и п р о и з в о д н о г о к л а с с о в : Point
pointArray
new
new new мы о б р а б а т ы в а е м этот массив в цикле: foreach if(obj
(object is
obj
in
pointArray)
ColorPoint)
A
Фролов, Г. R
Самоучитель
else
(obj
is
Point)
Здесь мы ПО очереди и з в л е к а е м все э л е м е н т ы м а с с и в а и з а п и с ы в а е м ссылку на них в п е р е м е н н у ю o b j класса
Класс
б а з о в ы м д л я всех к л а с с о в
в п р о г р а м м а х С # , поэтому такая о п е р а ц и я всегда з а к о н ч и т с я у с п е х о м . И з в л е к а я о ч е р е д н о й э л е м е н т массива, п р о г р а м м а не знает, какой тип и м е е т этот элемент. У нас в о з м о ж н ы д в а случая
я ч е й к а м а с с и в а с о д е р ж и т ссылку на б а з о в ы й
класс P o i n t или п р о и з в о д н ы й класс C o l o r P o i n t . С п о м о щ ь ю о п е р а т о р а is мы м о ж е м о п р е д е л и т ь тип с с ы л к и и в ы п о л н и т ь соответ с т в у ю щ е е д е й с т в и е . Е с л и это с с ы л к а на о р д и н а т ы и цвет, а если « ч е р н о - б е л а я »
т о ч к у , мы в ы в о д и м на экран ее ко о г р а н и ч и в а е м с я только к о о р д и н а т а м и .
Таким образом, оператор as позволяет выполнить безопасное преобразование типов, а оператор
определить тип данных динамически во время работы программы.
Нестандартное преобразование И н о г д а при создании п р о г р а м м т р е б у е т с я в ы п о л н и т ь п р и в е д е н и е о д н о г о с о з д а н н о г о вами типа к д р у г о м у . Для р е ш е н и я п о д о б н ы х задач в я з ы к е С# п р е д у с м о т р е н ы средст¬ ва я в н о г о и н е я в н о г о н е с т а н д а р т н о г о п р е о б р а з о в а н и я т и п о в . В е р н е м с я в н о в ь к к о м п л е к с н ы м ч и с л а м . И з в е с т н о , что к о м п л е к с н о е ч и с л о мож¬ но представить в виде точки на к о о р д и н а т н о й плоскости
[7]. При этом д е й с т в и
т е л ь н а я ч а с т ь к о м п л е к с н о г о ч и с л а с т а в и т с я в с о о т в е т с т в и е к о о р д и н а т о е п о оси X , а мнимая
по оси Y.
Р а н е е м ы у ж е создавали для п р е д с т а в л е н и я к о м п л е к с н ы х чисел класс C o m p l e x . В этом классе мы о п р е д е л и л и все н е о б х о д и м ы е п е р е г р у ж е н н ы е о п е р а т о р ы . К р о м е то¬ го, мы с о з д а в а л и и н е о д н о к р а т н о и с п о л ь з о в а л и в п р и м е р а х п р о г р а м м класс P o i n t , с о б о й точки на п л о с к о с т и . Возникает вопрос, можно ли приводить друг к другу ссылки на объекты этих классов? Если да, то мы с м о ж е м в ы п о л н я т ь п р и с в а и в а н и е с с ы л о к на о б ъ е к т ы этих к л а с с о в , как это с д е л а н о н и ж е : Point pt Complex с
new new
Complex r e s u l t l Point r e s u l t 2 Глава
Point(l,
2);
pt;
Преобразование типов объектов
З д е с ь мы два объекта классов C o m p l e x и P o i n t , а т а к ж е две локаль¬ ные п е р е м е н н ы е этих же классов. Затем п р о г р а м м а з а п и с ы в а е т в п е р е м е н н у ю класса C o m p l e x ссылку на о б ъ е к т класса P o i n t , а т а к ж е в п е р е м е н н у ю класса P o i n t ссылку н а о б ъ е к т класса C o m p l e x . На п е р в ы й в з г л я д т а к о е п р и с в а и в а н и е д о п у с т и м о , так как с у щ е с т в у е т о д н о з н а ч н о е с о о т в е т с т в и е м е ж д у д е й с т в и т е л ь н о й и м н и м о й частями к о м п л е к с н о г о числа и коор¬ д и н а т а м и (X, Y) т о ч к и . Однако компилятор не о существовании подобного соответствия. Он не обладает никакой информацией о том, как нужно преобразовывать объекты класса C o m p l e x в объ¬ екты класса P o i n t , а также о том, как выполнить обратное преобразование. Чтобы п р и в е д е н н ы й в ы ш е ф р а г м е н т кода т р а н с л и р о в а л с я без о ш и б о к , мы д о л ж н ы определить в классах C o m p l e x и P o i n t соответствующие методы, не¬ с т а н д а р т н о е п р е о б р а з о в а н и е между о б ъ е к т а м и этих классов. Н и ж е м ы п р и в е л и и с х о д н ы й текст м е т о д а , в ы п о л н я ю щ е г о п р и в е д е н и е типа ссылки н а о б ъ е к т класса P o i n t к типу с с ы л к и н а о б ъ е к т класса C o m p l e x : public
static
return
implicit
operator
Complex (Point
pt)
new
О б ъ я в л е н и е этого метода д о л ж н о н а х о д и т ь с я в классе C o m p l e x . Н а ш м е т о д с о з д а е т н о в ы й о б ъ е к т класса C o m p l e x , и н и ц и а л и з и р у я его соответст¬ в у ю щ и м о б р а з о м : к о о р д и н а т а по оси X з а п и с ы в а е т с я в р е а л ь н у ю часть к о м п л е к с н о г о числа, а к о о р д и н а т а по оси Y — в м н и м у ю часть к о м п л е к с н о г о числа. О б р а т и т е в н и м а н и е , что здесь м ы и с п о л ь з о в а л и к л ю ч е в ы е слова i m p l i c i t и operator. С ключевым словом
o p e r a t o r вы у ж е з н а к о м ы — оно и с п о л ь з у е т с я для пере¬
грузки о п е р а т о р о в (см. гл. 4). Что же касается i m p l i c i t , то это к л ю ч е в о е слово ука¬ зывает на допустимость неявного преобразования типов. Вместо
i m p l i c i t при о б ъ я в л е н и и н е с т а н д а р т н о г о п р е о б р а з о в а н и я м о ж н о указы¬
вать к л ю ч е в о е с л о в о e x p l i c i t . В этом с л у ч а е п р и д е т с я задавать тип п р е о б р а з о в а н и я Point pt Complex с
new new
Point(l, (3,
Complex r e s u l t l P o i n t result2 Хотя о п е р а т о р ы явного п р е о б р а з о в а н и я н е с к о л ь к о з а г р о м о ж д а ю т и с х о д н ы й текст, их п р и м е н е н и е п о з в о л и т с о к р а т и т ь к о л и ч е с т в о о ш и б о к при н а п и с а н и и п р о г р а м м с не¬ с т а н д а р т н ы м п р е о б р а з о в а н и е м т и п о в . Если н е с т а н д а р т н о е п р е о б р а з о в а н и е о б ъ я в л е н о как программист н е сможет п о ошибке выполнить неправильное приведе ние т и п о в — к о м п и л я т о р не даст ему этого сделать. Чтобы можно было приводить ссылку на объект класса C o m p l e x к ссылке типа P o i n t , в классе P o i n t необходимо объявить метод приведения типа следующего вида: 200
А В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
public
static
return
implicit
operator
new
P o i n t (Complex
c)
c.im);
Этот создает новый объект класса в качестве к о о р д и н а т ы по оси X и с п о л ь з у е т с я д е й с т в и т е л ь н а я часть к о м п л е к с н о г о числа, а в качестве коор¬ д и н а т ы по оси Y м н и м а я часть к о м п л е к с н о г о числа. В л и с т и н г е 5.5 мы привели и с х о д н ы й текст п р о г р а м м ы , д е м о н с т р и р у ю щ е й приме¬ нение о п и с а н н ы х в ы ш е н е с т а н д а р т н ы х п р е о б р а з о в а н и й . Листинг using
5.5.
Файл
System;
namespace class
Complex
public public
double double
public
Complex ( d o u b l e
re im
public
static
Complex
Complex
Sub(Complex -
Complex
operator
Complex
static
return Глава 5.
(Complex
operator
new
static
new
Complex
xl.im
xl,
Complex
-
operator
r e t u r n new public
x2)
Complex xl.im
new
static
return public
i)
Complex
new
static
return
public
double
new
static
return public
r,
r i
return public
re; im;
1, Complex
operator re -
типов объектов
1,
x) x.im
1); x)
Complex
x2)
public
static
return
Complex
operator
* (Complex xl,
Complex
operator
xl,
Complex
new -
public
static
return
Complex
x2)
new -
public
static
return else return public
static
operator
xl,
Complex
xl,
Complex
true; false; bool
operator
! = x2.im)
return else return public
false;
override
return public
true;
bool
o)
true;
override
int
return
public
static
return
public
public
operator
implicit
operator
Complex ( P o i n t
pt)
new
static
return
implicit
new
static
Complex(d,
implicit
Complex
d)
d o u b l e (Complex
c)
0);
operator
return
202
В Фролов, Г. В Фролов. Язык С#. Самоучитель
class
Point
public public
double double
public
P o i n t (double x,
xPos
public
xPos;
double
y)
x;
void
x,
double
y)
точки xPos yPos public
в
x,
у)
x; y; static
return
public
new
static
return public
new
static
return
implicit
operator
Point
P o i n t (Complex
c)
im)
implicit
operator
Point(d,
0);
implicit
operator
Point(double
double(Point
d)
pt)
pt.xPos;
class static
void
Point pt Complex с
args) new new
Complex Point r e s u l t 2
Point
Глава
pt
result3
Преобразование типов объектов
203
double
result4
с; result4);
О б р а т и т е в н и м а н и е , что в классе
мы дополнительно определили преоб
р а з о в а н и е д е й с т в и т е л ь н о г о ч и с л а типа d o u b l e в к о м п л е к с н о е ч и с л о , а
преобра¬
зование к о м п л е к с н о г о числа в д е й с т в и т е л ь н о е ч и с л о : public
static
return
public
new
static
implicit
operator
Complex(d,
implicit
Complex (double
d)
d o u b l e (Complex
c)
0)
operator
return
При в ы п о л н е н и и п р е о б р а з о в а н и я к о м п л е к с н о г о числа в д е й с т в и т е л ь н о е наш метод и г н о р и р у е т с у щ е с т в о в а н и е м н и м о й части числа. П р и о б р а т н о м п р е о б р а з о в а н и и мни¬ мая часть ч и с л а с т а н о в и т с я равной н у л ю . А н а л о г и ч н ы е м е т о д ы о п р е д е л е н ы и в классе P o i n t : public
static
implicit
r e t u r n new P o i n t ( d ,
public
static
return
Заметим,
implicit
operator
Point(double
d)
0)
operator
double(Point
pt)
pt.xPos;
что
с помощью нестандартных преобразований
вы
можете выполнить
п р и в е д е н и е л ю б ы х т и п о в о б ъ е к т о в , если, к о н е ч н о , это и м е е т с м ы с л и вы знаете алго¬ ритм т а к о г о п р и в е д е н и я . Хотя
классы
в С # н а с л е д у ю т с я о т о б щ е г о класса
все они
и м е ю т н е ч т о о б щ е е . О д н а к о это не означает, что вы д о л ж н ы п р е д у с м а т р и в а т ь приве¬ д е н и е т и п о в для всех р а з р а б о т а н н ы х вами к л а с с о в . Как и д р у г и е в о з м о ж н о с т и языка С#, эта в о з м о ж н о с т ь д о л ж н а и с п о л ь з о в а т ь с я т о л ь к о при н е о б х о д и м о с т и . Едва ли стоит п р е д у с м а т р и в а т ь п р и в е д е н и е а б с о л ю т н о н е с в я з а н н ы х м е ж д у собой т и п о в ( в с п о м н и т е п о г о в о р к у про бузину в о г о р о д е и д я д ь к у в К и е в е ) .
204
А В
Г. В. Фролов. Язык С# Самоучитель
Глава 6. Свойства объектов До сих пор мы р а с с м а т р и в а л и о б ъ е к т ы классов С#
как о б ъ е д и н е н и е п о л е й д а н н ы х
и м е т о д о в , п р е д н а з н а ч е н н ы х для р а б о т ы с этими полями д а н н ы х или для р е а л и з а ц и и д р у г и х свойств о б ъ е к т о в . В этой главе мы р а с с к а ж е м вам о так н а з ы в а е м ы х свойствах (properties), п р е д с т а в л я ю щ и х собой а т р и б у т , связанный с о б ъ е к т о м или к л а с с о м . Для
объяснения
назначения
свойств
мы
вернемся
к
описанию
класса
v i s i o n S e t , в п е р в ы е у п о м я н у т о г о нами в разделе « П е р в ы е шаги к О О П » гл. 3. На¬ п о м н и м , что этот класс п р е д с т а в л я л собой п р о г р а м м н у ю модель т е л е в и з о р а , к о т о р ы й м о ж н о было в к л ю ч а т ь и в ы к л ю ч а т ь , п е р е к л ю ч а т ь с канала на канал. К р о м е т о г о , мож¬ но было р е г у л и р о в а т ь г р о м к о с т ь звука. Н и ж е для у д о б с т в а мы в о с п р о и з в е л и в с о к р а щ е н н о м виде и с х о д н ы й текст класса
class
TelevisionSet
bool byte byte byte
Определить public bool
включен или выключен максимальный н о м е р к а н а л а текущий н о м е р к а н а л а текущая г р о м к о с т ь з в у к а
состояние
телевизора
205
включен
или
выключен
П е р е к л ю ч и т ь с я на прием з а д а н н о г о к а н а л а public bool SetChannel (byte channel)
Получить public byte
номер
текущего
канала
Установить громкость public void SetVolume ( b y t e volume)
Получить public byte
текущий
уровень
громкости
М ы м о ж е м п р е д с т а в и т ь себе т е л е в и з о р как о б ъ е к т , и м е ю щ и й с л е д у ю щ и е а т р и б у т ы : •
с о с т о я н и е т е л е в и з о р а ( в к л ю ч е н или в ы к л ю ч е н ) ,
•
м а к с и м а л ь н о д о п у с т и м ы й н о м е р канала,
•
т е к у щ и й н о м е р канала,
•
т е к у щ и й у р о в е н ь г р о м к о с т и звука.
З н а ч е н и е п е р е ч и с л е н н ы х в ы ш е а т р и б у т о в х р а н и т с я в п о л я х класса v i s i o n S e t , а д л я и з м е н е н и я этих з н а ч е н и й и для п о л у ч е н и я т е к у щ и х значений мы предусмотрели соответствующие методы. Например, текущий уровень громкости можно установить методом S e t V o l u m e , а определить методом G e t V o l u m e . Что б ы п е р е к л ю ч и т ь т е л е в и з о р н а н у ж н ы й канал, с л е д у е т вызвать м е т о д S e t C h a n n e l , для т о г о , ч т о б ы у з н а т ь н о м е р канала, п р и н и м а е м о г о т е л е в и з о р о м в н а с т о я щ и й мо¬ мент, — м е т о д G e t C h a n n e l . З а м е т и м , что в ы п о л н и т ь все п е р е ч и с л е н н ы е в ы ш е д е й с т в и я м о ж н о было бы и п о д р у г о м у , н а п р и м е р о б р а т и в ш и с ь н а п р я м у ю к полям класса, о б ъ я в л е н н ы м как p u b l i c . О д н а к о , д е й с т в у я п о д о б н ы м о б р а з о м , легко д о п у с т и т ь о ш и б к у . В с а м о м д е л е , ничто н е п о м е ш а е т п р о г р а м м е з а п и с а т ь в поле c u r r e n t C h a n n e l , х р а н я щ е е н о м е р текуще¬ го канала, с л и ш к о м б о л ь ш о е п р е в ы ш а ю щ е е м а к с и м а л ь н о д о п у с т и м о е . В ре¬ зультате п р о г р а м м а п р е д п р и м е т п о п ы т к у п е р е к л ю ч и т ь т е л е в и з о р н а н е с у щ е с т в у ю щ и й канал.
206
А В
Г. В. Фролов Язык
Самоучитель
С д р у г о й с т о р о н ы , п р я м о е о б р а щ е н и е к полям у п р о щ а е т и с х о д н ы й текст п р о г р а м мы и д е л а е т его н а г л я д н е е , так как для и з м е н е н и я а т р и б у т о в о б ъ е к т а в м е с т о в ы з о в а методов можно воспользоваться оператором присваивания. М е х а н и з м с в о й с т в , р е а л и з о в а н н ы й в я з ы к е п р о г р а м м и р о в а н и я С# и о п и с а н н ы й в этой главе нашей к н и г и , п о з в о л я е т с к о м б и н и р о в а т ь д о с т о и н с т в а и с п о л ь з о в а н и я ме¬ т о д о в для д о с т у п а к полям класса и п р я м о г о о б р а щ е н и я к этим п о л я м . Он п о з в о л я е т создать так н а з ы в а е м ы е поля (smart fields), д о с т у п к к о т о р ы м к о н т р о л и р у е т с я с п о м о щ ь ю п р о ц е д у р с п е ц и а л ь н о г о вида.
Объявление свойства К а ж д ы й класс С# м о ж е т с о д е р ж а т ь в себе о б ъ я в л е н и я о д н о г о или н е с к о л ь к и х с в о й с т в . С в о й с т в а п р е д с т а в л я ю т собой м е т о д ы с п е ц и а л ь н о г о вида, п р и ч е м для к а ж д о г о свойст¬ ва п р и м е н я ю т с я два т а к и х метода. Один м е т о д и с п о л ь з у е т с я для ч т е н и я з н а ч е н и я свойства, а д р у г о й — для з а п и с и . Для того чтобы объявить свойство, нам потребуется конструкция следующего вида: <Тип>
<Имя
get return
<Возвращаемое
значение>;
set <Поле
value;
Здесь в к а ч е с т в е м о д и ф и к а т о р а м о ж н о и с п о л ь з о в а т ь уже з н а к о м ы е вам к л ю ч е в ы е слова p u b l i c , p r o t e c t e d , p r i v a t e , i n t e r n a l , s t a t i c и n e w . Вот п р и м е р о б ъ я в л е н и я с в о й с т в а C h a n n e l в классе T e l e v i s i o n S e t : class
TelevisionSet
private public
byte byte
Channel
set if ( v a l u e currentchannel
value
0)
value;
get return
Глава 6. Свойства объектов
207
мы
указали м о д и ф и к а т о р д о с т у п а свойства p u b l i c , р а з р е ш а ю щ и й д о с т у п
к свойству C h a n n e l л ю б ы м Блок с в о й с т в а C h a n n e l
методам
программы.
Тип
byte, а
с о д е р ж и т две процедуры доступа (accessor).
—
О д н а из этих
п р о ц е д у р , о б ъ я в л е н н а я с к л ю ч е в ы м словом g e t , п р е д н а з н а ч е н а для п о л у ч е н и я значе¬ ния с в о й с т в а , а д р у г а я , о б ъ я в л е н н а я с к л ю ч е в ы м словом s e t , — для у с т а н о в к и значе¬ ния свойства.
Процедура доступа set С п о м о щ ь ю процедуры доступа s e t программа может установить значение свойства, используя обычный оператор присваивания. Рассмотрим следующий фрагмент программы: TelevisionSet TelevisionSet tvSmall tvLarge
tvSmall; tvLarge;
new new 5;
Здесь м ы с о з д а л и д в а о б ъ е к т а класса T e l e v i s i o n S e t , с о д е р ж а щ е г о о б ъ я в л е н и е свойства
Channel
(номер
текущего
канала).
Далее
один из этих т е л е в и з о р о в на 5-й к а н а л , а д р у г о й
наша
программа переключила
на 52-й к а н а л , в ы п о л н и в установку
свойства C h a n n e l в соответствующих объектах. Как в и д и т е , с с ы л к а на с в о й с т в а о б ъ е к т а в ы п о л н я е т с я т о ч н о т а к и м же о б р а з о м , как и на поле о б ъ е к т а . О д н а к о д е й с т в и я , в ы п о л н я е м ы е в п р о ц е с с е п р и с в а и в а н и я значения с в о й с т в у , п о л н о с т ь ю о т л и ч а ю т с я о т д е й с т в и й , в ы п о л н я е м ы х при и з м е н е н и и значения поля. К о г д а п р о г р а м м а и з м е н я е т с в о й с т в о о б ъ е к т а , п р и с в а и в а я ему з н а ч е н и е , выпол¬ няется с о о т в е т с т в у ю щ а я п р о ц е д у р а д о с т у п а .
Она,
например,
м о ж е т п р о в е р и т ь при¬
с в а и в а е м о е з н а ч е н и е на д о п у с т и м о с т ь , с о х р а н и т ь это з н а ч е н и е не в о п е р а т и в н о й памя¬ ти, а в базе д а н н ы х или д а ж е передать его через И н т е р н е т . В нашем
п р и м е р е п р о ц е д у р а д о с т у п а s e t с в о й с т в а C h a n n e l о б ъ я в л е н а в классе
TelevisionSet
следующим
образом:
set if ( v a l u e maxChannel value currentChannel value;
0)
О б р а т и т е в н и м а н и е н а и с п о л ь з о в а н и е к л ю ч е в о г о слова v a l u e .
Это слово обозна
чает н е я в н ы й п а р а м е т р , п е р е д а в а е м ы й п р о ц е д у р е д о с т у п а s e t . Этот п а р а м е т р содер¬ жит з н а ч е н и е , к о т о р о е п р о г р а м м а п ы т а е т с я п р и с в о и т ь свойству.
208
А. В
Г В.
Язык
Самоучитель
Наша п р о ц е д у р ы д о с т у п а s e t свойства C h a n n e l п р о в е р я е т допусти¬ мость в ы п о л н е н и я о п е р а ц и и п р и с в а и в а н и я , сравнивая з н а ч е н и е п а р а м е т р а v a l u e с максимально допустимым значением, а также с нулем. Изменение значения свойства п р о и с х о д и т только в том случае, если п р о г р а м м а задает д о п у с т и м ы й н о м е р канала. В п р о т и в н о м случае т е к у щ и й номер канала не и з м е н я е т с я . Д р у г о й способ о б р а б о т к и о ш и б о к , п р е д п о л а г а ю щ и й п р и м е н е н и е и с к л ю ч е н и й , будет р а с с м о т р е н п о з ж е в г л а в е , посвященной исключениям.
Процедура доступа get Процедура доступа g e t
п р е д н а з н а ч е н а для
получения текущего
значения свойства.
Она о б я з а т е л ь н о д о л ж н а в о з в р а щ а т ь это з н а ч е н и е с п о м о щ ь ю о п е р а т о р а r e t u r n . Вот п р и м е р о п р е д е л е н и я п р о ц е д у р ы д о с т у п а g e t с в о й с т в а го в классе
Channel,
объявленно
TelevisionSet:
return З а м е т и м , что з н а ч е н и е с в о й с т в а м о ж е т х р а н и т ь с я в поле (как в п р и м е р е , приведен¬ ном в ы ш е ) или в ы ч и с л я т ь с я д и н а м и ч е с к и во время р а б о т ы п р о г р а м м ы ( н а п р и м е р , на основе с о д е р ж и м о г о базы д а н н ы х или как-то еще). Ч т е н и е свойства в ы п о л н я е т с я а н а л о г и ч н о ч т е н и ю о б ы ч н о г о поля класса: канал "Включен"
из громкость "Выключен",
Так как ч т е н и е свойства не с в о д и т с я к п р о с т о м у к о п и р о в а н и ю с о д е р ж и м о г о поля о б ъ е к т а , а п р и в о д и т к вызову с о о т в е т с т в у ю щ е й п р о ц е д у р ы д о с т у п а , это дает програм¬ м и с т у в о з м о ж н о с т ь д е й с т в о в а т ь более гибко. О п р е д е л я я свойства и п р о ц е д у р ы д о с т у п а , п р о г р а м м и с т м о ж е т п о л н о с т ь ю с к р ы т ь р е а л и з а ц и и класса, о т в е ч а ю щ и е за х р а н е н и е д а н н ы х и способ их п р е д о с т а в л е ¬ ния в н е ш н е м у миру. Б о л е е т о г о , и з м е н е н и е этих деталей никак не с к а ж е т с я на работо¬ с п о с о б н о с т и п р о г р а м м , и с п о л ь з у ю щ и х классы с и н т е р ф е й с а м и .
Свойства только для чтения и только для записи Е с л и при о б ъ я в л е н и и с в о й с т в а о п р е д е л и т ь д в е п р о ц е д у р ы д о с т у п а s e t и g e t , то т а к о е с в о й с т в о б у д е т д о с т у п н о и д л я з а п и с и и для ч т е н и я . Но и н о г д а б ы в а е т по¬ л е з н о о б ъ я в и т ь т а к о е с в о й с т в о , к о т о р о е м о ж н о т о л ь к о ч и т а т ь или в к о т о р о е м о ж н о только писать. В я з ы к е С# очень л е г к о о б ъ я в и т ь с в о й с т в а , д о с т у п н ы е т о л ь к о для чтения или толь¬ к о для записи. Е с л и , н а п р и м е р , вам н у ж н о с в о й с т в о , к о т о р о е м о ж н о т о л ь к о ч и т а т ь , следует о п р е д е л и т ь м е т о д д о с т у п а g e t и н е о п р е д е л я т ь м е т о д д о с т у п а s e t . Объявле¬ ние свойства, д о с т у п н о г о т о л ь к о для з а п и с и , д о л ж н о , н а о б о р о т , с о д е р ж а т ь определе¬ ние о д н о г о только м е т о д а д о с т у п а s e t . Глава 6. Свойства объектов
209
В о т п р и м е р о б ъ я в л е н и я с в о й с т в а M a x C h a n n e l , д о с т у п н о г о т о л ь к о д л я чтения: class
TelevisionSet
private public
byte byte
MaxChannel
get return
Это с в о й с т в о х р а н и т м а к с и м а л ь н ы й н о м е р канала, к о т о р ы й м о ж е т п р и н и м а т ь теле¬ в и з о р . И н и ц и а л и з а ц и я поля m a x C h a n n e l , х р а н я щ е г о этот н о м е р , в ы п о л н я е т с я один раз к о н с т р у к т о р о м при с о з д а н и и объекта. П о с л е того как о б ъ е к т с о з д а н , п р о г р а м м а н е с м о ж е т и з м е н и т ь м а к с и м а л ь н ы й номер п р и н и м а е м о г о к а н а л а , так как поле m a x C h a n n e l о б ъ я в л е н о с м о д и ф и к а т о р о м досту¬ па p r i v a t e . П р и м е н е н и е с в о й с т в , д о с т у п н ы х только д л я ч т е н и я или т о л ь к о д л я з а п и с и , позво¬ ляет количество ошибок, допускаемых программистом в ис¬ п о л ь з о в а н и я о п е р а т о р о в п р и с в а и в а н и я и д р у г и х с р е д с т в д о с т у п а к д а н н ы м объекта.
Пример программы В л и с т и н г е 6.1 мы п р и в е л и и с х о д н ы й текст п р о г р а м м ы , д е м о н с т р и р у ю щ е й и с п о л ь з о в а н и е с в о й с т в о б ъ е к т о в , с о з д а в а е м ы х н а базе класса новой реали¬ зации этого класса м ы з а м е н и л и все м е т о д ы с о о т в е т с т в у ю щ и м и с в о й с т в а м и , з а м е т н о у п р о с т и в работу п р о г р а м м с о б ъ е к т а м и д а н н о г о класса. 6.1.
Файл
System; namespace TvProperties class
TelevisionSet
Конструктор
класса
TelevisionSet
public Устанавливаем исходное false; maxChannel
состояние
телевизора выключен количество
А В. Фролов, Г. В Фролов.
Самоучитель
currentchannel Volume
Свойство private public
PowerOn
bool bool
1; 10;
при включении п о к а з ы в а т ь канал г р о м к о с т ь при включении - 10%
включен
или
выключен
isPowerOn; PowerOn
get return
isPowerOn;
set isPowerOn
Свойство private public
value;
MaxChannel
byte
максимальный
номер
maxChannel
byte
MaxChannel
get
Свойство private public
Channel
текущий
номер
канала
byte byte
Channel
get return set if ( v a l u e
Глава 6. Свойства объектов
maxChannel currentchannel
value 0) value;
канала
1
Свойство private public
Volume
текущий
уровень
громкости
byte byte
Volume
get return set 0 value currentVolume
100) value;
currentVolume
class
0;
TvPropertiesApp
static
void
args)
TelevisionSet TelevisionSet tvSmall tvLarge
tvSmall;
new T e l e v i s i o n S e t ( 6 ) new
50; true; 27;
Console
tvSmall:
PowerOn
канал "Включен"
из громкость "Выключен",
tvLarge: канал "Включен"
А В.
из
Г. В.
громкость
С# Самоучитель
80; 39;
tvSmall tvLarge
false;
Console
tvSmall:
Console PowerOn
канал "Включен"
из громкость "Выключен",
tvLarge:
PowerOn
канал "Включен"
из громкость "Выключен",
В п р о г р а м м е о б ъ я в л е н класс T e l e v i s i o n S e t , п р е д с т а в л я ю щ и й в и р т у а л ь н ы й те¬ л е в и з о р . Все у п р а в л е н и е этим в и р т у а л ь н ы м т е л е в и з о р о м р е а л и з о в а н о ч е р е з свойства. К о н с т р у к т о р класса T e l e v i s i o n S e t в ы п о л н я е т н а ч а л ь н у ю лей класса, у с т а н а в л и в а я тем самым и с х о д н о е с о с т о я н и е телевизора: public isPowerOn maxChannel
false;
currentVolume Все
поля,
выключен количество каналов при включении п о к а з ы в а т ь к а н а л г р о м к о с т ь при включении - 10%
1; 10;
инициализируемые
конструктором,
объявлены
в
классе
1
Televi-
как p r i v a t e , п о э т о м у объекты п р о г р а м м ы , в н е ш н и е п о о т н о ш е н и ю к дан¬ ному классу, не и м е ю т к этим п о л я м н и к а к о г о д о с т у п а . Чтобы п р о г р а м м а могла в к л ю ч а т ь и в ы к л ю ч а т ь в и р т у а л ь н ы й т е л е в и з о р , в классе TelevisionSet private public
bool bool
определено свойство isPowerOn;
PowerOn
get Глава 6. Свойства объектов
PowerOn:
return
isPowerOn;
set isPowerOn
value;
Ч т о б ы в к л ю ч и т ь т е л е в и з о р , в это с в о й с т в о н у ж н о записать л о г и ч е с к о е значение t r u e , а чтобы выключить — логическое значение f a l s e . Так как в м е т о д е P o w e r O n о п р е д е л е н ы две п р о ц е д у р ы д о с т у п а , s e t и g e t , про¬ г р а м м а м о ж е т не т о л ь к о в к л ю ч а т ь или в ы к л ю ч а т ь т е л е в и з о р , но и определять его те¬ к у щ е е с о с т о я н и е ( в к л ю ч е н или в ы к л ю ч е н ) . В ы ш е в этой главе мы уже р а с с к а з ы в а л и про с в о й с т в о M a x C h a n n e l , х р а н я щ е е м а к с и м а л ь н о д о п у с т и м ы й н о м е р канала: private public
byte byte
maxChannel; MaxChannel
get return
maxChannel;
Так как в этом с в о й с т в е о б ъ я в л е н а т о л ь к о одна п р о ц е д у р а д о с т у п а g e t , п р о г р а м м а с м о ж е т ч и т а т ь д а н н о е с в о й с т в о , но не з а п и с ы в а т ь в него н о в ы е з н а ч е н и я . С в о й с т в о C h a n n e l д о с т у п н о как для ч т е н и я , так и для записи: private public
byte byte
Channel
get return set if ( v a l u e maxChannel value currentChannel value;
0)
При ч т е н и и д а н н о г о с в о й с т в а п р о г р а м м а п о л у ч и т т е к у щ и й н о м е р канала, храня¬ щ и й с я в поле c u r r e n t C h a n n e l . Что же касается п о п ы т к и у с т а н о в и т ь н о в ы й н о м е р канала, то она будет т о л ь к о в том с л у ч а е , если п р о г р а м м а з а п и с ы в а е т в с в о й с т в о C h a n n e l д о п у с т и м о е
А. В
Г. В. Фролов. Язык
Самоучитель
з н а ч е н и е ( б о л ь ш е е нуля и м е н ь ш е е м а к с и м а л ь н о д о п у с т и м о г о н о м е р а к а н а л а ) . Попыт¬ ки п е р е к л ю ч и т ь т е л е в и з о р на н е д о п у с т и м ы й канал б у д у т п р о и г н о р и р о в а н ы . Аналогичным образом работает и свойство V o l u m e , хранящее текущую громкость звука: private public
byte
Volume
get return set 0 currentVolume else currentVolume
value
100)
П о п ы т к а у с т а н о в и т ь н е п р а в и л ь н о е з н а ч е н и е для г р о м к о с т и звука п р и в е д е т к т о м у , что звук будет п р о с т о о т к л ю ч е н . Теперь
м ы р а с с м о т р и м м е т о д M a i n , п о л у ч а ю щ и й у п р а в л е н и е при з а п у с к е про¬
граммы. С в о ю работу этот м е т о д н а ч и н а е т с т о г о , что создает два т е л е в и з о р а , один из кото¬ рых с п о с о б е н п р и н и м а т ь 6 к а н а л о в , а д р у г о й TelevisionSet TelevisionSet tvSmall tvLarge
new new
40 к а н а л о в :
tvLarge T e l e v i s i o n S e t (6)
Д а л е е п р о г р а м м а в к л ю ч а е т п е р в ы й т е л е в и з о р , п е р е к л ю ч а е т его на 5-й канал и у с т а н а в л и в а е т у р о в е н ь г р о м к о с т и , р а в н ы й 50 % от м а к с и м а л ь н о г о у р о в н я :
tvSmall
50,-
О б р а т и т е в н и м а н и е , что все эти д е й с т в и я в ы п о л н я ю т с я при п о м о щ и п р о с т о г о опе¬ ратора присваивания.
Программа записывает нужные значения
в
свойства объекта,
а м е т о д ы д о с т у п а s e t д е л а ю т все н е о б х о д и м ы е п р о в е р к и и д р у г и е д е й с т в и я . Д а л е е наша п р о г р а м м а в к л ю ч а е т второй т е л е в и з о р , п е р е к л ю ч а е т его на 27-й канал и у с т а н а в л и в а е т у р о в е н ь г р о м к о с т и 30
6. Свойства объектов
215
true;
Т е к у щ е е с о с т о я н и е п е р в о г о т е л е в и з о р а о т о б р а ж а е т с я на консоли:
PowerOn
канал "Включен"
из "Выключен",
громкость
О б р а т и т е в н и м а н и е , что для п о л у ч е н и я т е к у щ е г о с о с т о я н и я т е л е в и з о р а мы обраща¬ емся к с в о й с т в а м с о о т в е т с т в у ю щ е г о объекта. А н а л о г и ч н а я п р о ц е д у р а в ы п о л н я е т с я для в т о р о г о т е л е в и з о р а : tvLarge:
PowerOn
канал (1) и з громкость "Включен" "Выключен",
На в т о р о м этапе своей работы п р о г р а м м а и з м е н я е т с о с т о я н и е т е л е в и з о р о в , пере¬ к л ю ч а я их на д р у г и е к а н а л ы : 3,80; Channel
39,60;
п р о г р а м м а в ы к л ю ч а е т оба т е л е в и з о р а : false; И н а к о н е ц , н о в о е с о с т о я н и е т е л е в и з о р о в о т о б р а ж а е т с я на к о н с о л и : tvSmall:
PowerOn
канал "Включен"
PowerOn
канал "Включен"
из громкость "Выключен",
(1)
из громкость "Выключен",
В о т что вы у в и д и т е на экране к о м п ь ю т е р а , з а п у с т и в нашу п р о г р а м м у :
В Фролов, Г. В. Фролов. Язык
Самоучитель
tvSmall: tvLarge: tvSmall: tvLarge:
Телевизор Телевизор Телевизор
Включен, к а н а л 5 из 6, г р о м к о с т ь 50 Включен, канал 2 7 и з 4 0 , г р о м к о с т ь 3 0 Выключен, к а н а л 3 из б, г р о м к о с т ь 80 Выключен, к а н а л 3 9 и з 4 0 , г р о м к о с т ь 6 0
Как видите, механизм свойств позволил заменить все методы класса и свести выполне¬ ние всех действий над виртуальными телевизорами к простым операциям присваивания. Стоит ли вам в своих п р о г р а м м а х методы свойствами? О ч е в и д н о , что нет. П р и м е н е н и е свойств о п р а в д а н о в тех с л у ч а я х , когда н е о б х о д и м о к о н т р о л и р о в а т ь з н а ч е н и е к а к о г о - л и б о а т р и б у т а о б ъ е к т а , т а к о г о , как н о м е р канала т е л е в и з о р а или уро¬ вень г р о м к о с т и . С в о й с т в о связывается т о л ь к о с о д н и м з н а ч е н и е м . М е т о д ы же более у н и в е р с а л ь н ы . Вы м о ж е т е , н а п р и м е р , п е р е д а в а т ь м е т о д а м не¬ с к о л ь к о п а р а м е т р о в по ссылке или по з н а ч е н и ю , что н е в о з м о ж н о при и с п о л ь з о в а н и и с в о й с т в . Как и л ю б о е с р е д с т в о языка п р о г р а м м и р о в а н и я С#, свойства д о л ж н ы приме¬ няться т а м , где они д е й с т в и т е л ь н о н е о б х о д и м ы и п о м о г а ю т у п р о с т и т ь п р о г р а м м у , а т а к ж е у л у ч ш и т ь ее структуру.
свойств Если вы создаете производный класс, то можете унаследовать в дочернем классе свойства базового Для
При этом допускается создание виртуальных и абстрактных свойств. того
чтобы
объявить
виртуальное
свойство,
используйте
ключевое
слово
как это п о к а з а н о н и ж е : public
virtual
int
X
set xPos
value;
return
xPos;
get
О б ъ я в л е н и е в и р т у а л ь н о г о свойства м о ж е т с о д е р ж а т ь в себе одну или две процеду¬ ры д о с т у п а . О б ъ я в л е н и е а б с т р а к т н о г о метода д о с т у п а в ы п о л н я е т с я с п р и м е н е н и е м к л ю ч е в о г о
слова
abstract:
public
abstract
int
Y
Глава 6. Свойства объектов
217
О б р а щ а е м ваше в н и м а н и е н а т о , что п р о ц е д у р ы д о с т у п а в и р т у а л ь н о г о с в о й с т в а д о л ж н ы быть о п р е д е л е н ы в п р о и з в о д н о м классе. Р а с с м о т р и м и с х о д н ы й текст п р о г р а м м ы , д е м о н с т р и р у ю щ е й н а с л е д о в а н и е свойств ( л и с т и н г 6.2). Листинг
6.2.
Файл
using System; namespace abstract
class
Shape
protected int xPos; protected int yPos; p u b l i c Shape ( i n t x, xPos xPos
int
y)
X; y;
public
abstract
public
virtual
void int
x,
int
y)
X
set xPos
value;
return
xPos;
get
public
abstract
int
Y
set;
class
Point
Shape
public
P o i n t ( i n t x,
public
override
void
i n t y)
base
(x,
x, точки
t h i X this.Y
218
y)
в
x y;
А. В. Фролов, Г. В. Фролов.
С#. Самоучитель
public
override
int
X
set xPos
value;
return
xPos;
get
public
override
int
Y
set yPos
value;
return
class
Rectangle
Shape
int width; int public width height public
x,
int y,
int w,
int h)
void
x,
int
Р и с о в а н и е прямоугольника
public
y)
w; h;
override
this.Y
base(x,
в
X; y;
override
int
Y
set yPos
value;
return
yPos;
get
6. Свойства объектов
219
class static
void
args)
Shape
pt
new
Shape
rect
25);
new
4,
allShapes new new new new
allShapes[3]
new
10,
20);
[4] (1, 3, 2,
Point (31,
currentShape
1, 3,
4);
4);
in
allShapes)
В этой п р о г р а м м е о б ъ я в л е н а б с т р а к т н ы й класс S h a p e и п р о и з в о д н ы е от него клас сы P o i n t и R e c t a n g l e . В а б с т р а к т н о м классе мы о б ъ я в и л и два п о л я , x P o s ф и г у р ы — о б ъ е к т а класса, с о з д а н н о г о на базе класса и н и ц и а л и з и р у ю щ и й эти поля при с о з д а н и и объекта. К р о м е т о г о , в этом классе о б ъ я в л е н а б с т р а к т н ы й для р и с о в а н и я ф и г у р ы в з а д а н н о й т о ч к е к о о р д и н а т н о й ва, X и Y, х р а н я щ и е т е к у щ и е к о о р д и н а т ы ф и г у р ы . Н и ж е м ы п р и в е л и и с х о д н ы й т е к с т класса P o i n t : class
Point
P o i n t ( i n t x,
public
override
void
int y)
Draw(int
base
x,
(x,
int точки
public
метод Draw, предназначенный п л о с к о с т и , а т а к ж е два свойст¬
Shape
public
this.X this.Y
и y P o s , хранящие координаты S h a p e , а также конструктор,
y)
y) в
x; y;
override
int
X
set xPos 220
А В.
Г В
Язык С# Самоучитель
get return
public
xPos;
override
int
Y
set yPos get return
yPos;
К о н с т р у к т о р класса P o i n t в ы з ы в а е т к о н с т р у к т о р базового класса. Метод Draw, объявленный с ключевым словом o v e r r i d e , выводит сообщение о р и с о в а н и и точки в з а д а н н ы х к о о р д и н а т а х , а затем с о х р а н я е т эти к о о р д и н а т ы в свойствах о б ъ е к т а X и Y. В классе P o i n t мы о п р е д е л и л и с в о й с т в а X и Y с к л ю ч е в ы м с л о в о м o v e r r i d e , так как они п е р е о п р е д е л я ю т с о о т в е т с т в у ю щ и е свойства базового класса, п е р в о е из к о т о р ы х я в л я е т с я в и р т у а л ь н ы м , а в т о р о е абстрактным. В классе R e c t a n g l e мы п е р е о п р е д е л и л и только а б с т р а к т н о е с в о й с т в о Y (абст р а к т н о е с в о й с т в о должно быть п е р е о п р е д е л е н о в п р о и з в о д н о м классе). Ч т о же касает¬ ся с в о й с т в а X, то здесь и с п о л ь з у е т с я с о о т в е т с т в у ю щ е е с в о й с т в о базового класса. М е т о д M a i n нашей п р о г р а м м ы д е м о н с т р и р у е т н е к о т о р ы е п р и е м ы р а б о т ы с объек¬ тами базового класса и п р о и з в о д н ы х к л а с с о в . всего о н создаст н о в ы й о б ъ е к т класса P o i n t , сохраняя ссылку н а этот о б ъ е к т в п е р е м е н н о й базового класса Shape. Д а л е е этот о б ъ е к т р и с у е т с я при п о м о щ и метода D r a w : Shape
pt
new
Point(10,
В б а з о в о м классе м е т о д D r a w о б ъ я в л е н как а б с т р а к т н ы й . В д а н н о м случае исполь¬ зуется р е а л и з а ц и я этого м е т о д а из класса А н а л о г и ч н ы е д е й с т в и я в ы п о л н я ю т с я для о б ъ е к т а класса R e c t a n g l e : Shape
new R e c t a n g l e ( l , 12); своей р а б о т ы
и
4,
10,
20);
п р о г р а м м а создает массив о б ъ е к т о в классов
Point
Rectangle: allShapes
allShapes[l]
new
new new R e c t a n g l e ( 2 , new new
Глава 6. Свойства объектов
3, 2,
1, 3,
221
currentShape Содержимое
этого
in
allShapes)
массива рисуется
с помощью
виртуального
м е т о д а базового
класса D r a w .
Статические свойства С п о м о щ ь ю к л ю ч е в о г о слова s t a t i c м о ж н о о п р е д е л и т ь с т а т и ч е с к и е свойства. Пра¬ вила п р и м е н е н и я т а к и х с в о й с т в а н а л о г и ч н ы п р а в и л а м п р м е н е н и я с т а т и ч е с к и х полей и м е т о д о в . Д л я с с ы л к и на с т а т и ч е с к и е с в о й с т в а н е о б х о д и м о и с п о л ь з о в а т ь имя класса, а не с с ы л к у на о б ъ е к т класса. К р о м е т о г о , в о б ъ я в л е н и и с т а т и ч е с к о г о с в о й с т в а нельзя у п о т р е б л я т ь к л ю ч е в ы е слова v i r t u a l , a b s t r a c t и o v e r r i d e . В л и с т и н г е 6.3 мы п р и в е л и и с х о д н ы й т е к с т п р о г р а м м ы , в к о т о р о й д е м о н с т р и р у е т с я использование статических свойств. Листинг
6.3.
using System; namespace PropStatic class static ScrWidth ScrHeight
public
static
640; 480;
string
Version
get return
"Microsoft
private s t a t i c uint ScrWidth; public s t a t i c uint ScreenWidth get return
ScrWidth;
private s t a t i c uint ScrHeight; public static uint ScreenHeight get 222
А. В Фролов, Г.
Фролов. Язык
Самоучитель
return
ScrHeight;
class static
void
args) система: разрешение:
x
Здесь мы о б ъ я в и л и класс S y s l n f в к о т о р о м есть с т а т и ч е с к и й к о н с т р у к т о р и три статических ScreenWidth и В задачу с т а т и ч е с к о г о к о н с т р у к т о р а в х о д и т и н и ц и а л и з а ц и я д в у х с т а т и ч е с к и х по¬ л е й , х р а н я щ и х т е к у щ е е р а з р е ш е н и е экрана м о н и т о р а :
ScrWidth ScrHeight
640; 480;
В н а ш е м п р о с т е й ш е м п р и м е р е мы и н и ц и а л и з и р у е м эти поля ч и с л а м и , о д н а к о ре¬ альная п р о г р а м м а м о ж е т о п р е д е л и т ь ф а к т и ч е с к о е р а з р е ш е н и е и с о х р а н и т ь его в п о л я х класса. С в о й с т в а S c r e e n W i d t h и S c r e e n H e i g h t п о з в о л я ю т п о л у ч и т ь т е к у щ е е разре¬ ш е н и е с о о т в е т с т в е н н о по г о р и з о н т а л и и в е р т и к а л и : p r i v a t e s t a t i c u i n t ScrWidth; public s t a t i c uint ScreenWidth get return
ScrWidth;
private static uint public s t a t i c uint ScreenHeight get return
ScrHeight;
Глава 6. Свойства объектов
З а м е т и м , что п р о г р а м м а не с м о ж е т ределили т о л ь к о п р о ц е д у р у д о с т у п а g e t .
з н а ч е н и е этих с в о й с т в , так как мы оп
А н а л о г и ч н ы м о б р а з о м объявлен с т а т и ч е с к и й метод V e r s i o n : public
static
string
Version
get return
"Microsoft
Единственная процедура доступа позволяет извлечь название ОС. Для того ч т о б ы о т о б р а з и т ь з н а ч е н и е п е р е ч и с л е н н ы х выше с в о й с т в , мы использова ли в м е т о д е M a i n н а ш е й п р о г р а м м ы с л е д у ю щ и е строки: разрешение:
х
В о т что п о я в и т с я на экране в р е з у л ь т а т е р а б о т ы этих строк: Операционная Экранное р а з р е ш е н и е :
Microsoft 640 х 480
Windows
К а к в и д и т е , д л я с с ы л к и на свойства мы у к а з ы в а е м имя класса созда¬ вая при этом о б ъ е к т о в д а н н о г о класса. В н е ш н е о б р а щ е н и е к с т а т и ч е с к и м с в о й с т в а м а н а л о г и ч н о о б р а щ е н и ю к с т а т и ч е с к и м полям класса. О д н а к о , как вы у ж е знаете, свой¬ ства « у м н е е » о б ы ч н ы х п о л е й , так как они могут с о д е р ж а т ь о п р е д е л е н н у ю л о г и к у , реа¬ лизованную в процедурах доступа.
224
А В
Г В Фролов. Язык
Самоучитель
Глава 7. Массивы и индексаторы Если программа должна работать с набором объектов одинакового типа, во многих случа¬ ях удобно образовать из этих объектов структуру данных, массивом (array). К а ж д ы й э л е м е н т массива имеет свой н о м е р ( и н д е к с ) и х р а н и т один объект. Зная и н д е к с э л е м е н т а м а с с и в а , п р о г р а м м а с м о ж е т извлечь или о б н о в и т ь н у ж н ы й ей объект. З а м е т и м , что в о д н о м массиве м о г у т х р а н и т ь с я о б ъ е к т ы базового класса и произ¬ водных к л а с с о в . Р а н е е в нашей книге мы у ж е п р и в о д и л и п о д о б н ы е п р и м е р ы , д е м о н с т р и р у ю щ и е в о з м о ж н о с т и п о л и м о р ф и з м а в я з ы к е С#. В я з ы к е п р о г р а м м и р о в а н и я С# м а с с и в ы о б о з н а ч а ю т с я с п о м о щ ь ю к в а д р а т н ы х ско бок, р а с п о л о ж е н н ы х справа о т о б о з н а ч е н и я типа о б ъ е к т о в , с о с т а в л я ю щ и х м а с с и в . Н и же мы п р и в е л и п р и м е р о п р е д е л е н и я м а с с и в а из 10 ц е л ы х чисел: bonus
new
Здесь о б ъ я в л е н а ссылка b o n u s н а о д н о м е р н ы й для х р а н е н и я ц е л ы х чисел с о з н а к о м т и п а
массив,
содержащий
10 ячеек
Обратите внимание, что мы не просто объявили ссылку на массив, а сразу же создали массив оператором n e w , указав размер массива. Без этого программа не сможет использо вать ссылку для работы с массивом, так как в ней будет храниться значение При о п р е д е л е н и и массива не р е з е р в и р у е т с я п а м я т ь , п о э т о м у в о б ъ я в л е н и и ссылки размеры м а с с и в а не у к а з ы в а ю т с я . П о с л е в ы п о л н е н и я р е з е р в и р о в а н и я п а м я т и операто¬ ром n e w р а з м е р м а с с и в а с т а н о в и т с я ф и к с и р о в а н н ы м . В том с л у ч а е , когда п р о г р а м м и с т заранее не знает р а з м е р ы м а с с и в а , м о ж н о у к а з а т ь эти р а з м е р ы динамически во в р е м я работы п р о г р а м м ы , н а п р и м е р : int
BonusArraySize bonus2 new
5;
Здесь с о з д а е т с я м а с с и в , р а з м е р ы к о т о р о г о п р е д в а р и т е л ь н о з а п и с ы в а ю т с я в пере¬ менную с именем
Типы массивов Как и б о л ь ш и н с т в о я з ы к о в п р о г р а м м и р о в а н и я , я з ы к С# п о з в о л я е т с о з д а в а т ь одномер¬ ные и м н о г о м е р н ы е м а с с и в ы . К р о м е т о г о , в о з м о ж н о создание м а с с и в о в , с о д е р ж а щ и х д р у г и е м а с с и в ы . Р а с с м о т р и м с п о с о б ы о б ъ я в л е н и я м а с с и в о в п е р е ч и с л е н н ы х в ы ш е ти¬ пов и п р и е м ы работы с н и м и .
Одномерные массивы Одномерный м о ж н о п р е д с т а в и т ь себе в виде л и н е й н о й п о с л е д о в а т е л ь н о с т и ячеек, к а ж д а я из к о т о р ы х и м е е т свой н о м е р . С а м а я первая ячейка имеет н о м е р 0, вто¬ рая — 1 и т. д.
225 8
Язык
Самоучитель
На рис. 7.1 держащий
мы с х е м а т и ч е с к и п о к а з а л и о д н о м е р н ы й м а с с и в с и м е н е м A r r a y , со э л е м е н т о в . Эти э л е м е н т ы нумеруются о т 0 д о
|
7.1.
Одномерный массив Array
Ч т о б ы с о с л а т ь с я в п р о г р а м м е на я ч е й к у с з а д а н н ы м н о м е р о м , н е о б х о д и м о у к а з а т ь этот н о м е р в к в а д р а т н ы х с к о б к а х , р а с п о л о ж е н н ы х с п р а в а от и м е н и массива: bonus
new 5; 7 10;
Здесь мы с о з д а л и о д н о м е р н ы й м а с с и в b o n u s , з а п и с а л и в 3 п е р в ы е я ч е й к и этого м а с с и в а ч и с л а 5, 7 и а затем в ы в е л и с о д е р ж и м о е п р о и н и ц и а л и з и р о в а н н ы х т а к и м способом ячеек на консоль. В д а н н о м с л у ч а е б ы л а и с п о л ь з о в а н а так н а з ы в а е м а я динамическая инициализация массива. П р о г р а м м а и н и ц и а л и з и р у е т м а с с и в , з а п и с ы в а я о б ъ е к т ы в его я ч е й к и . В о з м о ж н а т а к ж е с т а т и ч е с к а я и н и ц и а л и з а ц и я м а с с и в а , когда с о д е р ж и м о е его я ч е е к о п р е д е л я е т с я в м о м е н т к о м п и л я ц и и п р о г р а м м ы . В о т п р и м е р с т а т и ч е с к о й инициализа¬ ции массива: int[]
bonusl
2,
Здесь п о с л е о п е р а т о р а п р и с в а и в а н и я мы у к а з а л и список инициализации массива, заключенный в фигурные скобки. О б р а т и т е в н и м а н и е , что при с т а т и ч е с к о й и н и ц и а л и з а ц и и нет н и к а к о й н е о б х о д и м о ¬ сти у к а з ы в а т ь р а з м е р м а с с и в а , так как он о п р е д е л я е т с я а в т о м а т и ч е с к и и с х о д я из количества элементов в списке инициализации. М а с с и в ы С# у д о б н ы т е м , что в них м о ж н о х р а н и т ь о б ъ е к т ы л ю б о г о типа. Н и ж е мы о б ъ я в и л и два с т р о к о в ы х м а с с и в а : Words new HelloWords
"C#",
М а с с и в H e l l o W o r d s п р о и н и ц и а л и з и р о в а н с т а т и ч е с к и , а его с о д е р ж и м о е отобра¬ ж а е т с я на к о н с о л и .
226
А В Фролов, Г.
Язык
Самоучитель
Многомерные массивы М н о г о м е р н ы е м а с с и в ы м о ж н о п р е д с т а в и т ь себе в виде м н о г о м е р н о й м а т р и ц ы , в узлах которой хранятся объекты. Н а р и с . 7.2 м ы показали п р и м е р д в у м е р н о г о м а с с и в а A r r a y . Столбцы Array Array[1,1] Аггау[2,0]
Array[2,1]
Array[2,2]
Array[3,1] Аггау[4,0]
Array[4,1]
j
Ячейка строка 3, столбец 2
7.2.
Двумерный
массив Array
Такой м а с с и в м о ж н о п р е д с т а в и т ь себе в виде строк и
столбцов. Для адресации
я ч е й к и д в у м е р н о г о м а с с и в а н у ж н о у к а з ы в а т ь два и н д е к с а — и н д е к с строки и и н д е к с столбца. Ч т о б ы создать д в у м е р н ы й м а с с и в , н а п р и м е р , ц е л ы х ч и с е л , и с п о л ь з у й т е конструк¬ ц и ю с л е д у ю щ е г о вида: TwoDimArray
new
int
Здесь при о б ъ я в л е н и и м а с с и в а м ы и с п о л ь з о в а л и з а п я т у ю д л я т о г о , ч т о б ы у к а з а т ь к о м п и л я т о р у н а н е о б х о д и м о с т ь с о з д а н и я ссылки
T w o D i m A r r a y н а д в у м е р н ы й мас¬
сив. К р о м е т о г о , в о п е р а т о р е n e w мы у к а з а л и к о л и ч е с т в о строк и с т о л б ц о в создавае¬ мого д в у м е р н о г о массива. Если н у ж н о о б ъ я в и т ь м н о г о м е р н ы й м а с с и в , и с п о л ь з у й т е н е с к о л ь к о з а п я т ы х : new Количество
запятых
должно
быть
равно
размерности
массива,
уменьшенной
на единицу. Таким образом, в п р е д ы д у щ е м примере мы создали ч е т ы р е х м е р н ы й массив. М н о г о м е р н ы е м а с с и в ы м о ж н о и н и ц и а л и з и р о в а т ь д и н а м и ч е с к и или с т а т и ч е с к и . Ниже приведен пример динамической инициализации двумерного массива: int
TwoDimArray 0]
new 5;
1]
2]
52 32;
Глава 7. Массивы и индексаторы
227
1], 2], П о с л е и н и ц и а л и з а ц и и с о д е р ж и м о е массива в ы в о д и т с я н а к о н с о л ь . Вот п р и м е р с т а т и ч е с к о й и н и ц и а л и з а ц и и д в у м е р н о г о м а с с и в а C r o s s Z e r o , кото рый м о ж н о и с п о л ь з о в а т ь , н а п р и м е р , для с о з д а н и я п р о г р а м м ы известной игры в крес¬ тики-нолики: CrossZero
0
В этом м а с с и в е х р а н я т с я с и м в о л ы типа c h a r .
Массивы массивов В я з ы к е С# д о п у с к а е т с я создавать м а с с и в ы м а с с и в о в , н а з ы в а е м ы е также несиммет м а с с и в а м и . Д р у г и е названия для н е с и м м е т р и ч н ы х м а с с и в о в , в с т р е ч а ю щ и е с я в л и т е р а т у р е и в д о к у м е н т а ц и и на я з ы к С # , — ступенчатые или (jagged) массивы. На рис. 7.3 мы показали массив, содержащий 5 одномерных массивов разного размера:
Аггау[1,3]
Аггау[3,1] |
|
Array[4,1]
Рис.
|
7.3.
|
|
|
Массив массивов Array
Первый массив содержит 2 ячейки, второй 4, т р е т и й 1 и т. д. И з о б р а ж е н и е та кого м а с с и в а п о х о ж е на л е с т н и ц у , о т с ю д а , в и д и м о , и п р о и з о ш е л п е р е в о д слов array как « с т у п е н ч а т ы й м а с с и в » . При н е о б х о д и м о с т и вы м о ж е т е о б ъ е д и н я т ь в м а с с и в ы не только о д н о м е р н ы е , но и м н о г о м е р н ы е м а с с и в ы . О д н а к о р а б о т а с такими о б ъ е к т а м и п о т р е б у е т от вас не¬ дюжинного пространственного воображения. О б ъ я в л е н и е м а с с и в а м а с с и в о в выполняется при п о м о щ и н е с к о л ь к и х пар квадрат ных скобок. Ниже, н а п р и м е р , мы о б ъ я в и л и массив м а с с и в о в , с о д е р ж а щ и х т е к с т о в ы е
new 228
А В Фролов, Г. В. Фролов.
Самоучитель
в н и м а н и е , что м ы указали р а з м е р н о с т ь н а ш е г о м а с с и в а м а с с и в о в , р а в н у ю д в у м . С о о т в е т с т в е н н о нам н е о б х о д и м о и н и ц и а л и з и р о в а т ь два м а с с и в а строк: new new После инициализации доступ к элементам массивов выполняется следующим образом: [0] [0]
"Hello,
-
C#";
"It"; "is"; "C#";
При п о м о щ и первой пары к в а д р а т н ы х скобок мы у к а з ы в а е м и н д е к с м а с с и в а , а при п о м о щ и второй и н д е к с э л е м е н т а в м а с с и в е . Я з ы к С# д о п у с к а е т с о з д а н и е д о в о л ь н о с л о ж н ы х м н о г о м е р н ы х м а с с и в о в , с о д е р ж а щ и х в себе д р у г и е м н о г о м е р н ы е м а с с и в ы . Н а п р и м е р , ниже мы объявили ссылку на двумерный массив, содержащий четырехмер¬ ный массив, который, в свою очередь, содержит трехмерный массив текстовых строк: VeryComplexArray; М ы , о д н а к о , н е р е к о м е н д у е м увлекаться с о з д а н и е м п о д о б н ы х к о н с т р у к ц и й без крайней на то н е о б х о д и м о с т и . Они могут запутать п р о г р а м м у и сделать ее исход¬ ный текст н е п о н я т н ы м не т о л ь к о д л я п о с т о р о н н и х л ю д е й , но и для самого разработчи¬ ка п р о г р а м м ы .
Пример программы В листинге
мы привели и с х о д н ы й текст п р о г р а м м ы ,
выполне¬
ние д е й с т в и й с о д н о м е р н ы м и , м н о г о м е р н ы м и и н е с и м м е т р и ч н ы м и м а с с и в а м и , опи¬ с а н н ы м и ранее в этой главе. Так как о т д е л ь н ы е ф р а г м е н т ы этой п р о г р а м м ы у ж е были о п и с а н ы , мы о с т а в л я е м ее вам для с а м о с т о я т е л ь н о г о и з у ч е н и я . Листинг
7.1.
Файл
u s i n g System; namespace class static
void bonus
1 J
new
10;
Глава 7. Массивы и индексаторы
229
int
BonusArraySize bonus2 new
bonusl Console ]= string[]
5;
2, = bonusl[l],
bonusl[0],
Words new HelloWords
"C#",
TwoDimArray 0] 0] 1] 1] 2]
new
int
5; 15; 5; 52;
JaggedArray new new
new
"Hello,
C#";
"is";
230
А. В. Фролов, Г. В Фролов.
Самоучитель
Массивы и циклы В п р е д ы д у щ и х п р и м е р а х п р о г р а м м мы о б р а щ а л и с ь к о т д е л ь н ы м я ч е й к а м м а с с и в о в , указывая их номер в цикле
с
(индекс).
использованием
Однако
таких
ч а щ е всего
операторов,
обработка массивов
как
for,
while,
do
выполняется и
foreach.
П р и этом п р о г р а м м а п о с л е д о в а т е л ь н о о б р а щ а е т с я к я ч е й к а м массива, з а п и с ы в а я или извлекая д а н н ы е .
Обработка одномерного массива чисел В н а ч а л е м ы р а с с м о т р и м самый п р о с т о й прием ц и к л и ч е с к о й о б р а б о т к и о д н о м е р н о г о м а с с и в а , о с н о в а н н ы й н а п р м е н е н и и о п е р а т о р а f o r ( л и с т и н г 7.2). Этот п р и е м и с п о л ь зуется во м н о г и х я з ы к а х п р о г р а м м и р о в а н и я , в ч а с т н о с т и в я з ы к а х С и С + + . Листинг
7.2.
Файл
using System; namespace ArrayLoop class
ArrayLoopApp
static
void
args)
Numbers int
for(i
new
i; 0;
i
1;
i
10;
i+ + )
i++)
В н у т р и м е т о д а M a i n м ы о б ъ я в и л и м а с с и в N u m b e r s , с о д е р ж а щ и й п е р е м е н н ы е ти па i n t :
Здесь мы не у к а з ы в а е м размер м а с с и в а , так как при о б ъ я в л е н и и массива память д л я него не р е з е р в и р у е т с я . Глава
Массивы и индексаторы
231
В
строке мы р е з е р в и р у е м п а м я т ь для х р а н е н и я 10 п е р е м е н н ы х типа Ссылку н а эту память м ы з а п и с ы в а е м в п е р е м е н н у ю N u m b e r s , з а в е р ш а я созда ние массива: Numbers
new
int
Д о п о л н и т е л ь н о мы о б ъ я в л я е м п е р е м е н н у ю i, которая будет п р и м е н я т ь с я для ин д е к с а ц и и массива: int
i;
Запись з н а ч е н и й в я ч е й к и массива (т. е. и н и ц и а л и з а ц и я массива) в ы п о л н я е т с я про¬ стым п р и с в а и в а н и е м в цикле: for(i
0;
i
10; i;
i++)
П о с л е з а в е р ш е н и я этого хранящиеся в массиве:
процесса
i
п р о г р а м м а о т о б р а ж а е т на консоли
значения,
i++)
WriteLine Здесь мы в н а ч а л е в ы в о д и м на к о н с о л ь с о д е р ж и м о е п е р в о г о э л е м е н т а массива, а за¬ тем и остальных: Numbers: О б р а т и т е в н и м а н и е на т о , как мы п р о в е р я е м у с л о в и е в ы х о д а за п р е д е л ы массива. П а р а м е т р цикла i с р а в н и в а е т с я со з н а ч е н и е м N u m b e r s Length. Что это за з н а ч е н и е ? Все м а с с и в ы в С# я в л я ю т с я о б ъ е к т а м и б и б л и о т е ч н о г о класса По л е L e n g t h этого класса с о д е р ж и т р а з м е р массива.
Обработка одномерного массива строк В л и с т и н г е 7.3 мы п р и в е л и п р и м е р п р о г р а м м ы , о т о б р а ж а ю щ е й на к о н с о л и содержи¬ мое о д н о м е р н о г о м а с с и в а строк, п р о и н и ц и а л и з и р о в а н н о г о с т а т и ч е с к и . 7.3.
Файл
using System; namespace ArrayLoopl ArrayLooplApp static
void
args) -
232
А
Г.
Фролов. Язык
Самоучитель
i i
i++)
О т о б р а ж е н и е э л е м е н т о в м а с с и в а на к о н с о л и в ы п о л н я е т с я в цикле и не ких о с о б е н н о с т е й : f o r ( i = l;
i
ника¬
i+ + )
В н а ч а л е на к о н с о л ь в ы в о д и т с я самый п е р в ы й э л е м е н т м а с с и в а с н у л е в ы м индек¬ сом, а затем все о с т а л ь н ы е : This
is
С#
Параметр
string
array
цикла и з м е н я е т с я о т е д и н и ц ы
до размера массива, равного
значению
Использование оператора foreach Хотя для ц и к л и ч е с к о й о б р а б о т к и м а с с и в о в м о ж н о и с п о л ь з о в а т ь л ю б ы е ц и к л и ч е с к и е о п е р а т о р ы (и д а ж е цикл, о р г а н и з о в а н н ы й с п о м о щ ь ю н е р е к о м е н д у е м о г о к использо¬ ванию оператора у д о б н е е всего п р и м е н я т ь оператор В л и с т и н г е 7.4 мы п р и в е л и второй в а р и а н т п р о г р а м м ы , о п и с а н н о й в п р е д ы д у щ е м р а з д е л е и п р е д н а з н а ч е н н о й для в ы в о д а на к о н с о л ь с о д е р ж и м о г о м а с с и в а , п р о и н и ц и а л и з и р о в а н н о г о статически. Листинг
7.4.
Файл
using System; namespace ArrayForeach class
ArrayForeachApp
static
void
Глава 7. Массивы и индексаторы
args)
233
string[]
Lines
foreach
(string
in
Lines)
Console
К а к в и д и т е , ц и к л о б р а б о т к и м а с с и в а в ы г л я д и т очень п р о с т о : foreach
(string
CurrentString
in
Lines)
В круглых скобках после оператора foreach мы объявили переменную C u r r e n t S t r i n g типа к о т о р о й в п р о ц е с с е о б р а б о т к и м а с с и в а L i n e s будет п о с л е д о в а т е л ь н о п р и с в а и в а т ь с я с о д е р ж и м о е к а ж д о й его я ч е й к и . Далее вы сможете использовать переменную C u r r e n t S t r i n g в теле цикла для про¬ смотра элементов массива. К с о ж а л е н и ю , оператор f o r e a c h не позволяет изменять со д е р ж и м о е элементов массива, но для просмотра массива (и, как вы узнаете позже, для про¬ смотра с о д е р ж и м о г о контейнеров объектов других типов) он очень удобен.
Многомерный массив объектов класса String Для р а б о т ы с м н о г о м е р н ы м и м а с с и в а м и у д о б н о и с п о л ь з о в а т ь в л о ж е н н ы е о п е р а т о р ы цикла, т а к и е , н а п р и м е р , как В л и с т и н г е 7.5 мы п р и в е л и и с х о д н ы й текст п р о г р а м м ы , к о т о р а я с о з д а е т д в у м е р н ы й м а с с и в т е к с т о в ы х с т р о к , з а п о л н я е т е г о , а затем в ы в о д и т с о д е р ж и м о е м а с с и в а на к о н с о л ь . Листинг
7.5.
Файл
using System; namespace class
ArrayMultiDimApp
static
void
args) A
Фролов, Г. R Фролов. Язык
Самоучитель
Colors int
i,
new
j ; 0;
i
for{j
i
2 0;
j
i
3;
2; j
j++)
i++) 3;
В н а ч а л е м ы о б ъ я в л я е м д в у м е р н ы й массив о б ъ е к т о в класса s t r i n g и сразу резер¬ в и р у е м для него п а м я т ь : Colors
new
Таким о б р а з о м , здесь создается м а с с и в из д в у х строк и ч е т ы р е х с т о л б ц о в . Д л я р а б о т ы со с т р о к а м и т а б л и ц ы мы будем и с п о л ь з о в а т ь п е р е м е н н ы е i (для п е р е бора строк т а б л и ц ы ) и j
(для п е р е б о р а с т о л б ц о в т а б л и ц ы ) :
int Массив C o l o r s заполняется в двойном вложенном цикле: for(i
i 0;
2; j
i+ + ) 3;
j++)
В каждую ячейку массива записывается строка вида C o l o r
х и
—
н о м е р строки и столбца т а б л и ц ы с о о т в е т с т в е н н о . В ы в о д с о д е р ж и м о г о т а б л и ц ы на консоль в ы п о л н я е т с я также в д в о й н о м в л о ж е н н о м ц и к л е , как это п о к а з а н о ниже:
Глава 7. Массивы и индексаторы
235
0
i
2
0;
i+
j
3;
j
Несимметричный массив объектов класса String В нашей с л е д у ю щ е й п р о г р а м м е ( л и с т и н г 7.6) мы д е м о н с т р и р у е м ц и к л и ч е с к у ю обра¬ ботку н е с и м м е т р и ч н о г о м а с с и в а т е к с т о в ы х строк. Листинг
7.6.
Файл
using namespace
static
void
args) Asymm;
int
i,
j new s t r i n g new new i 0;
i++) j
j++)
i for(j
0;
i++) j
j++) i
236
А В
[j
Г.
Фролов Язык С# Самоучитель
в
Определяя несимметричный с т о л б ц о в и строк:
массив
мы
сначала не у к а з ы в а е м , с к о л ь к о
Asymm; Д а л е е при р е з е р в и р о в а н и и памяти для массива мы задаем т о л ь к о к о л и ч е с т в о строк, равное двум: Asymm
new s t r n g 2
Затем в к а ж д о й строке задается разное к о л и ч е с т в о с т о л б ц о в : в первой строке — 3 с т о л б ц а , а во второй 4: new new И н и ц и а л и з и р у я несимметричный м а с с и в в д в о й н о м в л о ж е н н о м ц и к л е , мы не мо¬ жем п о л а г а т ь с я на т о , что каждая строка с о д е р ж и т о д и н а к о в о е к о л и ч е с т в о с т о л б ц о в . П о э т о м у п р е д е л ь н о е з н а ч е н и е п е р е м е н н о й в н у т р е н н е г о цикла о п р е д е л я е т с я как 0;
i 0;
i++) j
j++)
Для первой строки оно равно т р е м , а для второй — четырем. С о д е р ж и м о е н е с и м м е т р и ч н о г о массива р а с п е ч а т ы в а е т с я н а консоли образом: 0;
i 0;
следующим
i++) j
Length;
j++)
Здесь г р а н и ц ы п е р е м е н н ы х в н е ш н е г о и в н у т р е н н е г о ц и к л о в у к а з а н ы соответствен¬ но как L e n g t h и Asymm i
Индексаторы В п р е д ы д у щ е й главе мы р а с с к а з ы в а л и о с в о й с т в а х о б ъ е к т о в (properties), с п о м о щ ь ю к о т о р ы х м о ж н о создавать «умные» поля. Доступ к т а к и м полям о с у щ е с т в л я е т с я т о л ь ко с п о м о щ ь ю с п е ц и а л ь н ы х п р о ц е д у р g e t и s e t . Н а п о м н и м , что вы м о ж е т е п о л у ч а т ь и и з м е н я т ь з н а ч е н и е свойства с п о м о щ ь ю о б ы ч н о г о о п е р а т о р а п р и с в а и в а н и я , о д н а к о вместо п р о с т о г о к о п и р о в а н и я значения в ы п о л н я е т с я с о о т в е т с т в у ю щ а я п р о ц е д у р а дос¬ тупа. Эта п р о ц е д у р а м о ж е т , н а п р и м е р , с о х р а н я т ь з н а ч е н и е свойства не в поле класса, а в базе д а н н ы х или где-то е щ е , в ы п о л н я я д о п о л н и т е л ь н у ю о б р а б о т к у д а н н ы х . Глава
Массивы и индексаторы
237
В я з ы к е С# м о ж н о н а д е л и т ь лается с
не т о л ь к о поля, но и м а с с и в ы . Это де
п о м о щ ь ю к о н с т р у к ц и и , н а з ы в а е м о й индексатором (indexer).
Чтобы
н а з н а ч е н и е и н д е к с а т о р а было
более п о н я т н ы м , р а с с м о т р и м п р а к т и ч е с к и й
пример. П у с т ь нам н у ж н о где-то х р а н и т ь н а з в а н и я т е л е в и з и о н н ы х к а н а л о в . При этом каж¬ д о м у н о м е р у т е л е в и з и о н н о г о канала д о л ж н о с т а в и т ь с я в с о о т в е т с т в и е то или иное на¬ з в а н и е . Д о с т у п к н а з в а н и я м к а н а л о в д о л ж е н о с у щ е с т в л я т ь с я по н о м е р у канала, п р и ч е м в т о м с л у ч а е , если указан н е п р а в и л ь н ы й или н е с у щ е с т в у ю щ и й канал, в м е с т о названия п р о г р а м м а д о л ж н а п о л у ч и т ь строку «Канал н е д о с т у п е н » . Б е з у с л о в н о , эту задачу м о ж н о р е ш и т ь с п о м о щ ь ю о б ы ч н о г о о д н о м е р н о г о массива т е к с т о в ы х строк. При и н и ц и а л и з а ц и и м а с с и в а в его я ч е й к и с л е д у е т з а п и с а т ь н а з в а н и я к а н а л о в . По¬ л у ч а я н а з в а н и е канала по его н о м е р у , п р о г р а м м а д о л ж н а п р о в е р я т ь этот н о м е р на до¬ п у с т и м о с т ь . Е с л и у к а з а н п р а в и л ь н ы й н о м е р , п р о г р а м м а м о ж е т и з в л е ч ь н а з в а н и е кана ла из с о о т в е т с т в у ю щ е й я ч е й к и м а с с и в а , а если н е п р а в и л ь н ы й
вернуть строку «Ка¬
нал н е д о с т у п е н » . Для у п р о щ е н и я п р о г р а м м ы с названиями
каналов
в
нам х о т е л о с ь
каком-либо
бы
классе,
инкапсулировать например
в
алгоритм работы
классе
с
названием
С н а б д и в этот класс и н д е к с а т о р о м , м о ж н о о р г а н и з о в а т ь д о с т у п к на¬ з в а н и я м к а н а л о в , как будто бы они х р а н я т с я в о б ы ч н о м м а с с и в е : new ch[0] ch[l] ch[2] ch[3] ch[4]
"Мир "Наше
i string
0;
i
i++)
s
Как видите,
здесь м ы создали о б ъ е к т
к л а с с а C h a n n e l N a m e s , передав конст¬
р у к т о р у к о л и ч е с т в о к а н а л о в , р а в н о е пяти. Д а л е е м ы и н и ц и а л и з и р у е м с п и с о к , последо¬ в а т е л ь н о з а п и с ы в а я н а з в а н и я к а н а л о в в о б ъ е к т c h . П р и этом номер канала у к а з ы в а е т с я т а к и м же с п о с о б о м , как и и н д е к с э л е м е н т а м а с с и в а , — с п о м о щ ь ю к в а д р а т н ы х скобок. П о л у ч е н и е н а з в а н и я канала по его н о м е р у в ы п о л н я е т с я т о ж е с п р и м е н е н и е м квадрат¬ ных с к о б о к . Созданный таким
способом
о б ъ е к т ch
ведет себя п о д о б н о
массиву,
однако
он
п р е д с т а в л я е т собой н е ч т о б о л ь ш е е , чем о б ы ч н ы й м а с с и в . Рассмотрим
и с х о д н ы й текст п р о г р а м м ы ,
в
которой
определен
класс
Channel-
N a m e s ( л и с т и н г 7.7).
238
Фролов, Г, В. Фролов. Язык
Самоучитель
7.7.
Листинг
Файл
using System; namespace class
ChannelNames
private private
uint
public
Channels; ArraySize;
ChannelNames ( u i n t
Channels ArraySize public
Count)
new Count;
uint
Size
get return
public
ArraySize;
string
this[uint
index]
get 0 return else return
"Канал
index
недоступен";
set 0
index
class static
void
args)
ChannelNames
ch[l] ch[2]
new
"Мир "Боевик";
Глава 7. Массивы и индексаторы
239
i
0;
i
i++)
s
В C h a n n e l N a m e s мы о б ъ я в и л и с с ы л к у на м а с с и в т е к с т о в ы х строк C h a n n e l s . С о о т в е т с т в у ю щ е е поле и м е е т м о д и ф и к а т о р д о с т у п а p r i v a t e , поэтому д о с т у п к н е м у в о з м о ж е н т о л ь к о для м е т о д о в класса C h a n n e l N a m e s : private
Channels;
К р о м е т о г о , в классе о б ъ я в л е н о поле S i z e , х р а н я щ е е р а з м е р массива названий ка¬ налов: private
uint
К э т о м у п о л ю т о ж е и м е ю т д о с т у п т о л ь к о ч л е н ы класса C h a n n e l N a m e s . К о н с т р у к т о р класса C h a n n e l N a m e s создает массив з а д а н н о г о р а з м е р а и сохраня¬ е т этот р а з м е р в п о л е A r r a y S i z e : public
ChannelNames ( u i n t
Channels ArraySize
Count)
new Count;
Чтобы программа, внешняя о т н о ш е н и ю к классу C h a n n e l N a m e s , могла опреде лять количество элементов в массиве объявили в классе свойство S i z e : public
uint
Size
get return
не
ArraySize;
В этом с в о й с т в е п р е д у с м о т р е н т о л ь к о один м е т о д д о с т у п а g e t , п о э т о м у п р о г р а м м а с м о ж е т и з м е н и т ь с о д е р ж и м о е поля A r r a y S i z e после с о з д а н и я о б ъ е к т а класса
Объявление индексатора Теперь public
мы
п е р е х о д и м к с а м о м у и н т е р е с н о м у — к объявлению
string
index]
get
240
А В Фролов, Г. В.
Язык
Самоучитель
0 return else return
"Канал
index
недоступен";
set 0
index value;
Как в и д и т е , о б ъ я в л е н и е и н д е к с а т о р а очень п о х о ж е на о б ъ я в л е н и е свойства. В нем тоже м о г у т быть п р о ц е д у р ы д о с т у п а g e t и s e t , п р и ч е м д о п у с к а е т с я о б ъ я в л я т ь л и б о обе эти п р о ц е д у р ы , л и б о только к а к у ю - т о одну из них. П р о ц е д у р ы д о с т у п а п о л н о с т ь ю у п р а в л я ю т п р о ц е с с о м записи д а н н ы х в « у м н ы й » м а с с и в , а т а к ж е и з в л е ч е н и е м д а н н ы х из этого массива. При о б ъ я в л е н и и и н д е к с а т о р а , как и при о б ъ я в л е н и и свойства, мы д о л ж н ы у к а з а т ь м о д и ф и к а т о р д о с т у п а и тип. В н а ш е м случае мы создали о б щ е д о с т у п н ы й и н д е к с а т о р , указав м о д и ф и к а т о р д о с т у п а p u b l i c . Так как названия к а н а л о в п р е д с т а в л я ю т собой т е к с т о в ы е с т р о к и , тип и н д е к с а т о р а задан как s t r i n g . К л а с с , с о д е р ж а щ и й и н д е к с а т о р , в ы с т у п а е т в роли массива. С с ы л к а на такой м а с с и в в ы п о л н я е т с я л и б о с и с п о л ь з о в а н и е м и м е н и объекта класса, л и б о ч е р е з имя класса (ес¬ ли и н д е к с а т о р о б ъ я в л е н как с т а т и ч е с к и й ) . П о э т о м у для и н д е к с а т о р а не т р е б у е т с я ука¬ зывать к а к о е - т о о с о б е н н о е и м я . В качестве имени и н д е к с а т о р а в ы с т у п а е т к л ю ч е в о е слово t h i s , о б о з н а ч а ю щ е е ссылку н а о б ъ е к т д а н н о г о класса. П о с л е к л ю ч е в о г о слова t h i s в о б ъ я в л е н и и и н д е к с а т о р а следует п а р а м е т р , заклю¬ ч е н н ы й в к в а д р а т н ы е скобки. Э т о т п а р а м е т р играет роль и н д е к с а и в н а ш е м случае имеет ц е л о ч и с л е н н ы й тип При в ы м о ж е т е и с п о л ь з о в а т ь д л я ин дексации п а р а м е т р ы л ю б о г о т и п а , н а п р и м е р текстовые с т р о к и . В п р о ц е д у р а х д о с т у п а g e t и s e t п а р а м е т р и н д е к с а т о р а п р и м е н я е т с я д л я получе¬ ния д о с т у п а к э л е м е н т у массива. В н а ш е м случае н а з в а н и я к а н а л о в х р а н я т с я в п р о с т о м о д н о м е р н о м м а с с и в е т е к с т о в ы х строк C h a n n e l s . О д н а к о при н е о б х о д и м о с т и п р о ц е дура д о с т у п а м о г л а бы извлекать эти н а з в а н и я из базы д а н н ы х или п о л у ч а т ь через И н тернет, п о л ь з у я с ь для и д е н т и ф и к а ц и и канала з н а ч е н и е м п а р а м е т р а i n d e x . П р о ц е д у р а д о с т у п а s e t и н д е к с а т о р а , так ж е как и а н а л о г и ч н а я п р о ц е д у р а д о с т у п а с в о й с т в а , п о л ь з у е т с я к л ю ч е в ы м с л о в о м v a l u e для у с т а н о в к и нового з н а ч е н и я масси¬ ва. При этом н у ж н ы й э л е м е н т массива и д е н т и ф и ц и р у е т с я при п о м о щ и п а р а м е т р а ин¬ дексатора. Приведенная выше реализация процедуры доступа g e t выполняет необходимый нам а л г о р и т м о п р е д е л е н и я н а з в а н и я канала по его номеру. Если указан н е п р а в и л ь н ы й н о м е р , она в о з в р а щ а е т строку «Канал н е д о с т у п е н » . Что же касается п р о ц е д у р ы д о с т у п а то она тоже в ы п о л н я е т н е к о т о р ы е про¬ верки. В ч а с т н о с т и , при у к а з а н и и н е д о п у с т и м о г о номера канала она и г н о р и р у е т по¬ пытку записи в м а с с и в нового з н а ч е н и я . и индексаторы
241
Индексаторы многомерных массивов В п р е д ы д у щ е м разделе мы п о к а з а л и , как п о л ь з о в а т ь с я и н д е к с а т о р а м и для «интеллектуального»
доступа
к
одномерному
массиву
строк.
При
необходимости
вы т а к ж е м о ж е т е создавать и н д е к с а т о р ы и для м н о г о м е р н ы х м а с с и в о в . Теперь н е м н о г о у с о в е р ш е н с т в у е м п р о г р а м м у , и с х о д н ы й текст которой был приве ден в л и с т и н г е 7.7. Мы п о т р е б у е м , чтобы п р о г р а м м а х р а н и л а для к а ж д о г о н о м е р кана¬ ла не о д н о , а два н а з в а н и я . П е р в о е название пусть будет с о о т в е т с т в о в а т ь , н а п р и м е р , отечественному каналу, а второе — зарубежному. Для р е а л и з а ц и и этой логики нам п о т р е б у е т с я д в у м е р н ы й массив т е к с т о в ы х строк. В новом в а р и а н т е п р о г р а м м ы ( л и с т и н г 7.8) мы о б ъ я в и л и такой м а с с и в , а т а к ж е индек¬ сатор для о р г а н и з а ц и и к нему « и н т е л л е к т у а л ь н о г о » д о с т у п а . Листинг
7.8.
Файл
using System; namespace class
ChannelNames
private private public
Channels; ArraySize;
uint
ChannelNames ( u i n t
Channels ArraySize
public
uint
Count)
new s t r i n g [ C o u n t , Count;
Size
get return
public
ArraySize;
string
this[uint
index,
uint
language]
get 0 return else return
242
index
"Канал
А В.
Г. В Фролов. Язык
Самоучитель
set if ( i n d e x
0
index language]
value;
class static
void
args)
ChannelNames ch[0, ch[l,
ch[2,
ch[0, ch[l, ch ch[3,
0] 0] 0] 0]
new
"Спорт";
"Наше кино"
1] 1] 1] 1]
for(uint
ch
"Discovery" "Fashion"; i
0;
i
i++)
string s
ch[i,
С с ы л к а на д в у м е р н ы й м а с с и в о б ъ я в л е н а с л е д у ю щ и м о б р а з о м : private
0],
ch[i,
1]
*
Channels;
Она и н и ц и а л и з и р у е т с я в к о н с т р у к т о р е , з а д а ю щ е м к о л и ч е с т в о строк и с т о л б ц о в д в у м е р н о г о массива: public
ChannelNames ( u i n t
Channels ArraySize
Count)
new Count;
К о л и ч е с т в о строк о п р е д е л я е т с я п а р а м е т р о м к о н с т р у к т о р а , а к о л и ч е с т в о с т о л б ц о в фиксировано и равно двум. Для индексатора нам теперь потребуется два параметра, первый из которых будет за¬ давать номер канала, а второй номер языка (0 для русского языка, и 1 для английского):
Глава 7. Массивы и индексаторы
243
public
string
index,
uint
language]
get 0 return else return
index
"Канал
недоступен";
set 0
Здесь
index language]
процедура доступа
get
value;
в о з в р а щ а е т н а з в а н и е канала с
заданным
и на з а д а н н о м я з ы к е или строку «Канал н е д о с т у п е н » в случае о ш и б к и . П р о ц е д у р а дос¬ тупа s e t и з м е н я е т н а з в а н и е з а д а н н о г о канала с у ч е т о м н о м е р а я з ы к а . В теле метода M a i n , получающего управление сразу после запуска п р о г р а м м ы , про грамма создает объект ch класса C h a n n e l N a m e s , передавая конструктору значение 5: ChannelNames
ch
new C h a n n e l N a m e s
В р е з у л ь т а т е этот о б ъ е к т с м о ж е т х р а н и т ь и н ф о р м а ц и ю о пяти к а н а л а х на одном из д в у х языков. Далее п р о г р а м м а инициализирует список каналов, обращаясь неявным образом к индексатору класса C h a n n e l N a m e s : ch[0, ch[2, ch[4,
ch[l, ch[2, ch[3,
0] 0] 0] 0] 0]
"Наше к и н о " t
1) 1] 1] 1] 1)
"Discovery" "TV
-
Как в и д и т е , в н а ч а л е и н и ц и а л и з и р у ю т с я р у с с к и е к а н а л ы , затем — з а р у б е ж н ы е . После инициализации
программа отображает на экране полный
список каналов,
также неявно обращаясь к индексатору: for(uint string
244
i
0; s
i String
i+ + ) ch[i,
0],
ch[i,
В Фролов, Г. В. Фролов. Язык
Самоучитель
В о т что п о я в и т с я на консоли после запуска нашей п р о г р а м м ы : Спорт Мир кино ( D i s c o v e r y ) Боевик (TV 5) Наше кино ( F a s h i o n )
Дополнительные операции с м а с с и в а м и в С# Р а с с к а з ы в а я о таких типах д а н н ы х , как i n t , c h a r и т. п., мы о б р а щ а л и в а ш е в н и м а на т о , что эти типы д а н н ы х созданы на базе с о о т в е т с т в у ю щ и х к л а с с о в . Что же ка сается м а с с и в о в , т о они тоже с о з д а н ы н а базе класса
Функциональ
ность, и н к а п с у л и р о в а н н а я в этом классе, н а д е л я е т массивы С# д о п о л н и т е л ь н ы м и воз¬ можностями,
недоступными
в
массивах,
реализованных
средствами других языков
п р о г р а м м и р о в а н и я . М а с с и в ы С# в о т л и ч и е от м а с с и в о в С + + , м о ж н о , н а п р и м е р , копи¬ ровать и с о р т и р о в а т ь . В
этом
разделе
мы
рассмотрим
наиболее полезные методы
и
с в о й с т в а класса
к о т о р ы е в ы м о ж е т е п р и м е н я т ь при р а б о т е с м а с с и в а м и .
Определение размера массива Ранее в н а ш и х п р о г р а м м а х мы у ж е о п р е д е л я л и к о л и ч е с т в о э л е м е н т о в , и м е ю щ и х с я в м а с с и в е , о б р а щ а я с ь д л я этого к с в о й с т в у L e n g t h . С у щ е с т в у ю т и д р у г и е средства, п о з в о л я ю щ и е п о л у ч и т ь
информацию о размере
массива. Н а п р и м е р , с п о м о щ ь ю с в о й с т в а R a n k м о ж н о у з н а т ь р а з м е р н о с т ь ( р а н г ) масси¬ ва.
Для
о д н о м е р н ы х массивов значение ранга равно е д и н и ц е , для д в у м е р н ы х —
д в у м и т. д. Методы
и
G e t U p p e r B o u n d п о з в о л я ю т узнать
соответственно
м и н и м а л ь н о е и м а к с и м а л ь н о е з н а ч е н и е индекса э л е м е н т о в , х р а н я щ и х с я в м а с с и в е . В
п а р а м е т р о в этим м е т о д а м н у ж н о п е р е д а т ь з н а ч е н и е ранга м а с с и в а , умень¬
ш е н н о е на е д и н и ц у . Для и з м е н е н и я з н а ч е н и я , х р а н я щ е г о с я в я ч е й к е массива, м о ж н о и с п о л ь з о в а т ь не только к в а д р а т н ы е с к о б к и , но и метод S e t V a l u e . При р а б о т е с о д н о м е р н ы м и масси¬ вами в качестве первого п а р а м е т р а этому м е т о д у н у ж н о п е р е д а т ь и з м е н я е м о е значе¬ н и е , а в качестве второго — и н д е к с с о о т в е т с т в у ю щ е й я ч е й к и массива. На наш взгляд, однако,
использование
с к о б о к п р е д с т а в л я е т собой
более
наглядный
способ
работы
с массивами. В л и с т и н г е 7.9 мы привели п р и м е р п р о г р а м м ы , д е м о н с т р и р у ю щ е й и с п о л ь з о в а н и е п е р е ч и с л е н н ы х выше свойств и м е т о д о в . Глава 7. Массивы и индексаторы
245
Листинг
7.9.
Файл
using System; namespace class static
void
args) 3, 5, 7, массива
Console
i
9,
in
изменения
i
элемента
in
массива: граница граница
массива: массива:
Console ReadLine
П о л у ч и в у п р а в л е н и е , п р о г р а м м а с о з д а е т м а с с и в ц е л ы х чисел и н и ц и а л и з и р у я его с т а т и ч е с к и : int[]
arrayOfNumbers
3,
5,
7,
9,
Далее п р о г р а м м а о т о б р а ж а е т н а к о н с о л и к о л и ч е с т в о э л е м е н т о в ( ч и с е л ) , х р а н я щ и х ся в м а с с и в е , а т а к ж е в ы в о д и т все эти э л е м е н т ы на К О Н С О Л Ь : массива i
in i
246
А В
Г.
Язык
Самоучитель
К о л и ч е с т в о э л е м е н т о в , х р а н я щ и х с я в м а с с и в е , извлекается и з с в о й с т в а L e n g t h ,
о п р е д е л е н н о г о в классе Далее мы з а п и с ы в а е м в ячейку массива с и н д е к с о м 5 з н а ч е н и е , р а в н о е 14, в ы з ы в а я для этого м е т о д
А н а л о г и ч н ы й р е з у л ь т а т м о ж н о п о л у ч и т ь и с п о м о щ ь ю к в а д р а т н ы х скобок:
И з м е н и в з н а ч е н и е э л е м е н т а м а с с и в а , п р о г р а м м а вновь в ы в о д и т его с о д е р ж и м о е на к о н с о л ь . На с л е д у ю щ е м этапе своей р а б о т ы п р о г р а м м а в ы в о д и т на консоль р а з м е р н о с т ь м а с с и в а , о б р а щ а я с ь для ее о п р е д е л е н и я к свойству R a n k : массива: И н а к о н е ц , перед тем как з а в е р ш и т ь свое в ы п о л н е н и е , п р о г р а м м а в ы в о д и т на кон¬ соль м и н и м а л ь н о е и м а к с и м а л ь н о е з н а ч е н и е и н д е к с а для э л е м е н т о в , х р а н я щ и х с я в массиве: граница
массива:
граница
массива:
О б р а т и т е в н и м а н и е , что у нас о д н о м е р н ы й р а з м е р , ранг к о т о р о г о равен е д и н и ц е . Тем не м е н е е мы п е р е д а е м м е т о д а м
и
GetUpperBound
нулевое
з н а ч е н и е , т. е. значение р а н г а , у м е н ь ш е н н о е на е д и н и ц у .
Сортировка и реверсирование массивов С помощью статических методов Sort и R e v e r s e м о ж н о соответст¬ венно о т с о р т и р о в а т ь э л е м е н т ы массива и п е р е с т а в и т ь их в о б р а т н о м п о р я д к е . Исполь¬ з о в а н и е этих м е т о д о в д е м о н с т р и р у е т с я в п р о г р а м м е , и с х о д н ы й т е к с т к о т о р о й приве¬ ден в л и с т и н г е 7.10. Листинг
7.10,
Файл
using System; namespace SortReverse class
SortReverseApp
static
void
args) 3,
Console
"Исходный i
Главв
51,
7,
29,
массив:
in
Массивы и индексаторы
i);
247
массив: i
in
i
in i);
В ней о б ъ я в л я е т с я массив ц е л ы х ч и с е л с п р и м е н е н и е м с т а т и ч е с к о й и н и ц и а л и з а ц и и : int[]
arrayOfNumbers
3,
51,
7,
29,
Далее наша программа сортирует массив, вызывая метод A r r a y . S o r t :
С о д е р ж и м о е и с х о д н о г о и о т с о р т и р о в а н н о г о массива в ы в о д и т с я на к о н с о л ь при по¬ мощи п р о с т е й ш е г о цикла: i
in i);
Э т о т же цикл п р и м е н я е т с я и для в ы в о д а с о д е р ж и м о г о м а с с и в а , э л е м е н т ы к о т о р о г о были п е р е с т а в л е н ы в о б р а т н о м при п о м о щ и метода A r r a y
В о т что н а ш а п р о г р а м м а в ы в о д и т н а к о н с о л ь : Исходный м а с с и в : 21 3 51 7 29 11 С о р т и р о в а н н ы й м а с с и в : 3 7 11 21 29 51 Р е в е р с и р о в а н н ы й м а с с и в : 51 29 21 11 7 3 З а м е т и м , что оба о п и с а н н ы х в ы ш е м е т о д а п о з в о л я ю т р а б о т а т ь с м а с с и в а м и л ю б ы х о б ъ е к т о в , а не т о л ь к о чисел. П р и н е о б х о д и м о с т и вы м о ж е т е о т с о р т и р о в а т ь м е т о д о м S o r t , н а п р и м е р , массив т е к с т о в ы х строк.
Поиск в массиве С помощью метода B i n a r y S e a r c h м о ж н о о р г а н и з о в а т ь поиск э л е м е н т о в в о д н о м е р н о м м а с с и в е . В к а ч е с т в е первого п а р а м е т р а этому методу н у ж н о п е р е д а т ь с с ы л к у на м а с с и в , а в качестве в т о р о г о — и с к о м ы й элемент. П р и у с п е х е ме¬ тод возвратит индекс найденного элемента, а в том с л у ч а е , если элемент не найден от¬ рицательное з н а ч е н и е . 248
А В Фролов, Г В. Фролов. Язык
Самоучитель
Исходный текст программы, демонстрирующей
применение метода
n a r y S e a r c h для поиска текстовой строки в м а с с и в е , п р и в е д е н в л и с т и н г е Листинг
7.11.
Файл
using System; namespace class
BinarySearchApp
static
void
args) arrayOfNumbers
string
searchString
int if
(index
0 \"{0}\"
не
else строки
\"{0}\"
равен
index
В п р о г р а м м е объявлен и п р о и н и ц и а л и з и р о в а н с т а т и ч е с к и массив т е к с т о в ы х строк arrayOfNumbers М е т о д M a i n ищет в м а с с и в е слово Array
вызывая для этого статический метод
Д а н н ы й м е т о д п о л у ч а е т в качестве п е р в о г о п а р а м е т р а ссыл¬
ку на м а с с и в , а в качестве в т о р о г о — ссылку на и с к о м у ю строку. Если искомая строка н е найдена, м е т о д A r r a y
в о з в р а щ а е т отри¬
ц а т е л ь н о е з н а ч е н и е . В случае успеха в о з в р а щ а е т с я и н д е к с н а й д е н н о й строки. Н а ш а п р о г р а м м а о т о б р а ж а е т на консоли и с к о м у ю строку и ее индекс. Глава
Массивы и индексаторы
249
Глава 8. Интерфейсы Как мы у ж е г о в о р и л и в гл. 3, класс С# м о ж е т н а с л е д о в а т ь с в о й с т в а т о л ь к о одного ба з о в о г о класса. Т а к и м о б р а з о м , в о т л и ч и е от д р у г и х я з ы к о в О О П ( н а п р и м е р , С++) в я з ы к е С# к а ж д ы й п р о и з в о д н ы й класс м о ж е т иметь только один базовый класс. На первый взгляд такое ограничение может показаться довольно существенным, одна ко во многих случаях оно у с п е ш н о обходится при помощи механизма интерфейсов. В то время как классы п р е д с т а в л я ю т собой м е х а н и з м для п р е д с т а в л е н и я с у щ н о с т е й ( т а к и х , н а п р и м е р , как г е о м е т р и ч е с к и е ф и г у р ы , т е л е в и з о р ы и т. п.), интер¬ фейсы п р и м е н я ю т с я для о п и с а н и я н е к и х д е й с т в и й над э т и м и с у щ н о с т я м и . П о н я т и е и н т е р ф е й с а о ч е в и д н о н а о б ы ч н о м ж и т е й с к о м у р о в н е . П р е д с т а в ь т е себе, н а п р и м е р , плеер для п р о и г р ы в а н и я к о м п а к т - д и с к о в . С у щ е с т в у е т б е с ч и с л е н н о е множе¬ ство р а з л и ч н ы х м о д е л е й т а к и х п л е е р о в , о т л и ч а ю щ и х с я друг о т д р у г а ф о р м о й к о р п у с а , ц в е т о м , р а з м е р о м и д р у г и м и а т р и б у т а м и . Тем не менее все они и м е ю т о д и н а к о в ы й н а б о р к н о п о к , с п о м о щ ь ю к о т о р ы х м о ж н о з а п у с к а т ь или о с т а н а в л и в а т ь п р о и г р ы в а н и е к о м п а к т - д и с к а , п е р е х о д и т ь с одной д о р о ж к и на д р у г у ю , а также извле¬ кать к о м п а к т - д и с к из к о р п у с а плеера. Такая у н и ф и к а ц и я « п о л ь з о в а т е л ь с к о г о и н т е р ф е й с а » п л е е р а п о з в о л я е т л ю б о м у из вас быстро о с в о и т ь н о в ы й п л е е р , не р а з б и р а я с ь в д е т а л я х его в н у т р е н н е г о устрой¬ ства. А н а л о г и ч н о вы л е г к о сумеете в о с п о л ь з о в а т ь с я б а н к о м а т о м л ю б о й модели для п о л у ч е н и я д е н е г с к р е д и т н о й к а р т о ч к и , так как все б а н к о м а т ы и м е ю т один и тот же «интерфейс». В о з в р а щ а я с ь к я з ы к у С # , з а м е т и м , что и с п о л ь з о в а н и е р а с с м о т р е н н ы х ранее с в о й с т в , и н д е к с а т о р о в , с о б ы т и й (о к о т о р ы х мы р а с с к а ж е м п о з ж е ) , а т а к ж е интерфей¬ сов п о з в о л я е т с о з д а в а т ь о б ъ е к т ы , к о т о р ы е м о ж н о и с п о л ь з о в а т ь , не вникая в детали их р е а л и з а ц и и . П р и этом о б ъ е к т (класс) м о ж е т реализовать набор и н т е р ф е й с о в , к а ж д ы й из к о т о р ы х о т в е ч а е т за в ы п о л н е н и е над о б ъ е к т о м к а к и х - л и б о д е й с т в и й . И н т е р ф е й с ы б о л е е всего п о х о ж и н а в и р т у а л ь н ы е м е т о д ы а б с т р а к т н о г о класса, ко т о р ы е д о л ж н ы быть о п р е д е л е н ы в б а з о в о м классе. Х о р о ш е й н о в о с т ь ю я в л я е т с я т о , что к а ж д ы й класс С # м о ж е т р е а л и з о в а т ь п р о и з в о л ь н о е к о л и ч е с т в о и н т е р ф е й с о в . И м е н н о это о б с т о я т е л ь с т в о д е л а е т н е с у щ е с т в е н н ы м о г р а н и ч е н и е С#, к а с а ю щ е е с я не¬ возможности множественного наследования классов.
Применение интерфейсов И н т е р ф е й с ы о б ъ я в л я ю т с я с п о м о щ ь ю к л ю ч е в о г о слова клас¬ сам. В н у т р и о б ъ я в л е н и я и н т е р ф е й с а н е о б х о д и м о п е р е ч и с л и т ь м е т о д ы , и з к о т о р ы х об¬ разуется и н т е р ф е й с . К р о м е м е т о д о в , внутри и н т е р ф е й с о в м о ж н о т а к ж е о б ъ я в л я т ь свойства, индексаторы и события. К л а с с , р е а л и з у ю щ и й и н т е р ф е й с , д о л ж е н с о д е р ж а т ь в себе тело м е т о д о в , объявлен¬ ных в р а м к а х всех р е а л и з у е м ы х им и н т е р ф е й с о в . Ч т о б ы это было п о н я т н е е , п р и в е д е м к о н к р е т н ы й п р и м е р .
250
Объявление интерфейса П у с т ь нам н у ж н о создать класс P o i n t , о т р а ж а ю щ и й п о в е д е н и е точки н а п л о с к о с т и . Этот о б ъ е к т д о л ж е н х р а н и т ь т е к у щ и е к о о р д и н а т ы т о ч к и , делая и х д о с т у п н ы м и при п о м о щ и свойств X и Y . К р о м е т о г о , класс P o i n t д о л ж е н два и н т е р ф е й с а I P r i n t и I M a i l , первый и з к о т о р ы х п о з в о л я е т р а с п е ч а т ы в а т ь точку н а п р и н т е р е и в ы п о л н я т ь п р е д в а р и т е л ь н ы й п р о с м о т р р е з у л ь т а т о в п е ч а т и , а второй — о т п р а в л я т ь точку по э л е к т р о н н о й почте по з а д а н н о м у адресу. Ниже м ы привели объявление интерфейсов I P r i n t и interface void void
IPrint Print
interface
IMail
void
(string
Как в и д и т е , в о б ъ я в л е н и и и н т е р ф е й с о в мы у к а з ы в а е м т о л ь к о п р о т о т и п ы м е т о д о в , не в к л ю ч а я тело этих м е т о д о в . За к о н к р е т н у ю р е а л и з а ц и ю м е т о д о в и н т е р ф е й с а отве¬ чает к л а с с , к о т о р ы й р е а л и з у е т этот и н т е р ф е й с . К р о м е т о г о , в п р о т о т и п а х м е т о д о в не т р е б у е т с я (и не д о п у с к а е т с я ) у к а з ы в а т ь мо¬ д и ф и к а т о р ы д о с т у п а , т а к и е , как p u b l i c или Все м е т о д ы и н т е р ф е й с а явля¬ ются о б щ е д о с т у п н ы м и п о у м о л ч а н и ю . Обратите также внимание на названия интерфейсов. В принято, что название ин терфейса начинается с прописной буквы I. И хотя это не строгое правило, мы рекоменду¬ ем его придерживаться, так как при этом исходный текст программы будет понятнее.
Реализация интерфейса При объявлении интерфейсов мы устанавливаем соглашение (контракт), которому д о л ж н ы у д о в л е т в о р я т ь м е т о д ы , с в о й с т в а , и н д е к с а т о р ы и с о б ы т и я , о б ъ я в л е н н ы е в рам¬ ках и н т е р ф е й с а . Ч т о же касается к о н к р е т н о й р е а л и з а ц и и и н т е р ф е й с а , то она, как мы уже г о в о р и л и , в о з л а г а е т с я н а класс, р е а л и з у ю щ и й и н т е р ф е й с . Н и ж е м ы п р и в е л и в с о к р а щ е н н о м виде и с х о д н ы й текст класса P o i n t , р е а л и з у ю щего и н т е р ф е й с ы I P r i n t и I M a i l : class
Point
protected protected public
IPrint, int int
IMail
xPos; yPos;
P o i n t ( i n t x,
int
y)
xPos yPos
Глава 8. Интерфейсы
251
void точки
this.X,
void
"Просмотр
перед
печатью
void
точки
this.X,
this.Y);
mailAddress)
ПО адресу
"Отправка т о ч к и this.X, this.Y,
Тот факт, что класс р е а л и з у е т те или и н ы е и н т е р ф е й с ы , о т р а ж а е т с я в к л ю ч е в о м слове c l a s s . Н а з в а н и я р е а л и з у е м ы х и н т е р ф е й с о в п е р е ч и с л я ю т с я после этого слова через з а п я т у ю : class
Point
IPrint,
IMail
Если бы наш класс P o i n t был у н а с л е д о в а н от б а з о в о г о класса с и м е н е м , напри¬ м е р , S h a p e и д о п о л н и т е л ь н о р е а л и з о в ы в а л и н т е р ф е й с ы I P r i n t и I M a i l , это м о ж н о было бы з а п и с а т ь с л е д у ю щ и м о б р а з о м : Point
Shape,
IPrint,
IMail
О д н а к о п р о с т о г о п е р е ч и с л е н и я р е а л и з у е м ы х и н т е р ф е й с о в н е д о с т а т о ч н о . В классе необходимо
расположить
тело
методов
этих
интерфейсов.
В
нашем
случае
класс
P o i n t д о л ж е н с о д е р ж а т ь тело всех м е т о д о в и н т е р ф е й с о в I P r i n t и I M a i l . О б р а т и т е в н и м а н и е , как м ы о п р е д е л и л и м е т о д P r i n t и н т е р ф е й с а I P r i n t : void точки
И м я м е т о д а у к а з а н о как I P r i n t
this.X,
this.Y);
Здесь мы с н а б д и л и имя м е т о д а п р е ф и к
сом в виде и м е н и и н т е р ф е й с а . Х о т я такой п р е ф и к с н е о б я з а т е л е н ,
же л у ч ш е его
у к а з ы в а т ь . Это п о з в о л и т и з б е ж а т ь н е о д н о з н а ч н о с т и , если класс р е а л и з у е т и н т е р ф е й с ы с
одинаковыми
и
названиями
методов.
Например,
если
бы
в
интерфейсах
IPrint
был о п р е д е л е н м е т о д , при р е а л и з а ц и и м е т о д о в мы могли бы их р а з л и ч а т ь
п о полным именам
252
и
C h e c k соответственно.
А В
Г В Фролов
Самоучитель
Вызов методов интерфейса П о с л е того как мы о б ъ я в и л и и н т е р ф е й с и р е а л и з о в а л и к употреблению.
в к л а с с е , и н т е р ф е й с готов
Как же им п о л ь з о в а т ь с я ? Д о с т а т о ч н о просто. В н а ч а л е н у ж н о создать о б ъ е к т класса, р е а л и з у ю щ е г о наш ин¬ терфейс: Point pt; pt new
20);
Далее нам н у ж н о создать ссылку на и н т е р ф е й с . Это делается с л е д у ю щ и м о б р а з о м : IPrint
ptPrinter
(IPrint)pt;
Здесь м ы о б ъ я в и л и п е р е м е н н у ю p t P r i n t e r т и п а I P r i n t и з а п и с а л и в нее ссыл¬ ку, п р и в е д я ее тип я в н ы м о б р а з о м к типу I P r i n t . С п о м о щ ь ю такой ссылки м о ж н о а д р е с о в а т ь с я к м е т о д а м и н т е р ф е й с а I P r i n t , но нельзя п о л у ч и т ь д о с т у п к п о л я м и ме т о д а м класса
Таким о б р а з о м , о б р а щ а я с ь к объекту через и н т е р ф е й с , п р о г р а м м а м о ж е т не и м е т ь никакой и н ф о р м а ц и и о т н о с и т е л ь н о т о г о , какие с в о й с т в а , поля и м е т о д ы о п р е д е л е н ы в этом классе. Этим д о с т и г а е т с я н е з а в и с и м о с т ь п р о г р а м м ы , р а б о т а ю щ е й с о б ъ е к т о м , от в н у т р е н н и х д е т а л е й р е а л и з а ц и и объекта.
Пример программы В л и с т и н г е 8.1
мы п р и в е л и п о л н ы й и с х о д н ы й т е к с т п р о г р а м м ы , д е м о н с т р и р у ю щ е й
объявление, реализацию и использование описанных выше интерфейсов. Листинг using
8.1.
Файл
System;
namespace interface void void interface
RectPrintable IPrint Print
IMail
void
Интерфейсы
253
class
Point
protected protected public
IPrint, int int
xPos; yPos;
Point ( i n t x,
xPos yPos
public
IMail
int
y)
x; y;
int
X
set xPos
value;
return
xPos;
get
public
int
Y
set yPos
value;
return
yPos;
get
void точки
this.X,
void
"Просмотр this.X,
void
254
перед
печатью
(string
точки
mailAddress)
А В Фролов, Г В Фролов.
Самоучитель
"Отправка точки this.X, this.Y,
адресу
2
class static
void
args)
Point pt; pt new IPrint
IMail
ptPrinter
(IPrint)pt;
ptMailer
В классе P o i n t определен конструктор, а также свойства X и Y, предназначенные для работы с координатами точки. Этот класс р е а л и з у е т интерфейсы I P r i n t и для чего в нем р а с п о л о ж е н о объявление м е т о д о в PrintPreview и IMail Эти м е т о д ы стимулируют выполне ние операций, отображая текстовые на консоли.
Проверка реализации интерфейса В предыдущем разделе мы описали один из возможных способов использования интер основанный на явном приведении типа ссылки на объект класса к типу Point p t ; pt new P o i n t IPrint ptPrinter
2 0);
Здесь м ы создали объект p t класса P o i n t ,
а также п е р е м е н н у ю p t P r i n t e r ,
для хранения ссылки на интерфейс переменной p t к типу
З а т е м мы привели тип
с помощью скобок.
На следующем этапе аналогичная операция была проведена и для интерфейса IMail
ptMailer
Глава 8 Интерфейсы
(IMail)pt;
255
Так как класс P o i n t оба и н т е р ф е й с а I P r i n t и I M a i l , в процессе пре¬ о б р а з о в а н и я т и п о в у нас не в о з н и к а е т никаких п р о б л е м . О д н а к о если бы мы попыта¬ лись привести с с ы л к у на класса к типу ссылки на и н т е р ф е й с , р е а л и з а ц и я кото¬ рого о т с у т с т в у е т в этом к л а с с е , такая о п е р а ц и я вызвала бы и с к л ю ч е н и е на этапе вы¬ полнения программы. И з у ч и м эту с и т у а ц и ю на п р и м е р е п р о г р а м м ы , и с х о д н ы й текст к о т о р о й приведен в л и с т и н г е 8.2. Листинг using
8.2.
ch08\ITelevision\ITelevisionApp.cs
Файл
System;
namespace
ITelevision
interface
IChannel
void uint
interface void uint
IVolume
current
interface
uint
ITunning
current
class
IChannel,
private private public
uint uint
IVolume
volume;
TvSet()
channel volume
1;
void channel
256
В.
Г, В. Фролов. Язык
Самоучитель
uint return
void volume
volumeLevel;
uint return
class
volume;
RadioSet
private private
uint uint
ITunning,
IVolume
channel;
public channel volume
1;
void
void 0)
return
void
setLevel (uint
volumeLevel)
volume uint return
volume;
Глава 8. Интерфейсы
257
class static
void
args)
TvSet tv; tv new T v S e t IChannel IVolume
channelTv
RadioSet radio; radio new ITunning tuneRadio IVolume v o l R a d i o
канал
канал
if(tv
is
громкость
громкость
ITunning)
ITunning
tuneTv
else ITunning ITunning
tuneTvl tv null)
as
ITunning; ITunning
мы объявили interface
не
не
реализован")
IChannel, IVolume и ITunning:
IChannel
void uint
258
В.
Г. В
Язык
Самоучитель
interface
IVolume
void uint
interface
ITunning
И н т е р ф е й с I C h a n n e l п р е д н а з н а ч е н для и с п о л ь з о в а н и я в в и р т у а л ь н о м т е л е в и з о р е или л ю б о м д р у г о м п о д о б н о м у с т р о й с т в е . Он п о з в о л я е т п е р е к л ю ч и т ь у с т р о й с т в о на за¬ д а н н ы й н о м е р канала, а т а к ж е п о л у ч и т ь н о м е р т е к у щ е г о у с т а н о в л е н н о г о канала. С п о м о щ ь ю и н т е р ф е й с а I V o l u m e п р о г р а м м а м о ж е т у с т а н о в и т ь или о п р е д е л и т ь текущий уровень громкости устройства. И н т е р ф е й с I T u n n i n g т о ж е п о з в о л я е т п е р е к л ю ч а т ь каналы и о п р е д е л я т ь н о м е р т е к у щ е г о канала. В о т л и ч и е от и н т е р ф е й с а I C h a n n e l , о д н а к о , с п о м о щ ь ю д а н н о г о и н т е р ф е й с а н е в о з м о ж н о п е р е к л ю ч и т ь с я н а канал с з а д а н н ы м н о м е р о м . М е т о д ы n e x t и p r e v интерфейса I T u n n i n g дают возможность последовательно переключаться на с л е д у ю щ и й и п р е д ы д у щ и й канал. Как видите, данные интерфейсы можно с успехом использовать для управления л ю б ы м и у с т р о й с т в а м и , в к о т о р ы х есть в о з м о ж н о с т ь у с т а н о в к и к а н а л о в и г р о м к о с т и . В н а ш е й п р о г р а м м е мы создали классы для м о д е л и р о в а н и я д в у х т а к и х у с т р о й с т в телевизора и радиоприемника: class
IChannel,
private private
uint uint
IVolume
channel; volume;
public channel volume
class
1; 10;
RadioSet
private private
uint uint
ITunning,
IVolume
channel; volume;
public channel volume
1; 10;
Глава 8. Интерфейсы
259
Класс T v S e t
моделирует телевизор (фактически только возможность переключе
ния каналов и р е г у л и р о в к и г р о м к о с т и ) , а класс R a d i o S e t
радиоприемник.
Оба эти класса р е а л и з у ю т и н т е р ф е й с I V o l u m e , п р е д н а з н а ч е н н ы й для р е г у л и р о в к и г р о м к о с т и . Что же касается п е р е к л ю ч е н и я к а н а л о в , то т е л е в и з о р м о ж н о п е р е к л ю ч а т ь на л ю б о й канал с з а д а н н ы м н о м е р о м п о с р е д с т в о м и н т е р ф е й с а I C h a n n e l , а прием¬ ник — только п е р е с т р а и в а т ь на с о с е д н и е каналы с п о м о щ ь ю и н т е р ф е й с а I T u n n i n g . Н и ж е м ы п р и в е л и и с х о д н ы й текст р е а л и з а ц и и и н т е р ф е й с а I C h a n n e l , предусмот¬ ренной в классе T v S e t : void channel
uint С return
channel;
Метод з а п и с ы в а е т н о в ы й н о м е р канала в поле класса c h a n n e l , а метод c u r r e n t в о з в р а щ а е т з н а ч е н и е , х р а н я щ е е с я в э т о м поле. Р е а л и з а ц и я и н т е р ф е й с а I T u n n i n g , и м е ю щ а я с я в классе R a d i o S e t , н е н а м н о г о сложнее: id channel+ void 0)
uint return М е т о д n e x t у в е л и ч и в а е т н о м е р к а н а л а на е д и н и ц у при к а ж д о м о б р а щ е н и и , а ме¬ тод p r e v у м е н ь ш а е т этот н о м е р , при у с л о в и и , что о н б о л ь ш е нуля. С п о м о щ ь ю метода c u r r e n t п р о г р а м м а м о ж е т о п р е д е л и т ь т е к у щ и й н о м е р канала. Что же касается и н т е р ф е й с а I V o l u m e , то он р е а л и з о в а н о д и н а к о в о и в классе T v S e t и в классе R a d i o S e t : void volume
260
В. Фролов. Г. В. Фролов. Язык
Самоучитель
uint return
volume;
Метод
setLevel
позволяет
установить
c u r r e n t — определить
нужный
уровень
громкости,
а
метод
громкости.
И т а к , т е п е р ь , когда у нас есть 3 и н т е р ф е й с а и 2 класса, р е а л и з у ю щ и х эти интер¬ ф е й с ы , мы м о ж е м и с п ы т а т ь все это в р а б о т е . Вначале
метод
Main
нашей
программы
а т а к ж е два и н т е р ф е й с а переключать каналы, а второй
создает
объект-телевизор
tv
класса
первый из которых позволяет регулировать громкость:
TvSet tv; tv new T v S e t IChannel channelTv IVolume v o l T v Для и с п о л ь з о в а н и я и н т е р ф е й с о в м ы в ы з ы в а е м с о о т в е т с т в у ю щ и е м е т о д ы :
Здесь мы п е р е к л ю ч а е м т е л е в и з о р на
10-й канал и у с т а н а в л и в а е м у р о в е н ь г р о м к о
сти, р а в н ы й 5 0 А н а л о г и ч н ы м о б р а з о м создается о б ъ е к т - р а д и о п р и е м н и к и ссылки на и н т е р ф е й с ы для у п р а в л е н и я р а д и о п р и е м н и к о м : RadioSet radio; radio new ITunning tuneRadio IVolume v o l R a d i o Сразу
после
с о з д а н и я р а д и о п р и е м н и к будет п р и н и м а т ь
1-й канал
(это з а д а е т с я
с о о т в е т с т в у ю щ и м к о н с т р у к т о р о м ) . Н а ш а п р о г р а м м а п е р е к л ю ч а е т его н а с л е д у ю щ и й , 2-й к а н а л , а затем у с т а н а в л и в а е т у р о в е н ь г р о м к о с т и , р а в н ы й 30
П о с л е в ы п о л н е н и я всех этих д е й с т в и й н а ш а п р о г р а м м а в ы в о д и т на к о н с о л ь теку¬ щие номера к а н а л о в , а т а к ж е у р о в н и г р о м к о с т и т е л е в и з о р а и п р и е м н и к а : канал
канал
Глава 8. Интерфейсы
громкость
громкость
261
Для п о л у ч е н и я этих значений в ы з ы в а ю т с я м е т о д ы c u r r e n t с о о т в е т с т в у ю щ и х ин¬ терфейсов. А что будет, если мы п о п ы т а е м с я и с п о л ь з о в а т ь и н т е р ф е й с I T u n n i n g для управ¬ ления т е л е в и з о р о м ? Такая п о п ы т к а м о ж е т в ы г л я д е т ь с л е д у ю щ и м о б р а з о м : ITunning
tuneTv
Здесь мы п о п ы т а л и с ь п р е о б р а з о в а т ь с с ы л к у tv на о б ъ е к т класса T v S e t в тип ин¬ терфейса
ITunning,
чтобы
в дальнейшем
использовать
методы
этого
интерфейса
n e x t и p r e v для последовательного переключения каналов. Компилятор
не
найдет
в
этой
строке
никаких
I T u n n i n g был о п р е д е л е н в н а ш е й п р о г р а м м е . денной
ошибок,
так
как
интерфейс
О д н а к о п о п ы т к а и с п о л н е н и я приве¬
с т р о к и вызовет и с к л ю ч е н и е
Это
и с к л ю ч е н и е п о я в и т с я п о т о м у , что и н т е р ф е й с I T u n n i n g не был р е а л и з о в а н в классе TvSet.
Таким о б р а з о м , наш т е л е в и з о р «не умеет» п е р е к л ю ч а т ь каналы с п о м о щ ь ю
этого и н т е р ф е й с а . Ч т о б ы н е д о п у с т и т ь а в а р и й н о г о з а в е р ш е н и я п р о г р а м м ы , н е о б х о д и м о либо обрабо¬ тать и с к л ю ч е н и е (о т о м , как это с д е л а т ь , мы р а с с к а ж е м п о з ж е ) , л и б о и с п о л ь з о в а т ь в процессе преобразования типов операторы Оператор
или a s .
i s п р и м е н я е т с я в н а ш е й п р о г р а м м е вместе с у с л о в н ы м о п е р а т о р о м
if
следующим образом: is
ITunning)
ITunning
tuneTv
else ITunning
не
Здесь мы в н а ч а л е п р о в е р я е м д о п у с т и м о с т ь п р е о б р а з о в а н и я и т о л ь к о затем выпол¬ няем это п р е о б р а з о в а н и е . Если ж е п р е о б р а з о в а н и е н е д о п у с т и м о , п р о г р а м м а в ы в о д и т предупреждающее сообщение на консоль. Д р у г о й с п о с о б основан н а п р и м е н е н и и о п е р а т о р а a s : ITunning
tuneTvl tv null)
as
ITunning; ITunning
не
В т о м с л у ч а е , когда п р е о б р а з о в а н и е н е д о п у с т и м о , этот о п е р а т о р вернет з н а ч е н и е null.
Программе
остается
только
проверить
результат
выполнения
операции
as,
предприняв соответствующие действия. З а м е т и м , что в т о р о й с п о с о б п р е о б р а з о в а н и я э ф ф е к т и в н е е , так как он и с к л ю ч а е т н е о б х о д и м о с т ь д в о й н о й п р о в е р к и д о п у с т и м о с т и п р е о б р а з о в а н и я , н е и з б е ж н о й в случае и с п о л ь з о в а н и я о п е р а т о р а is.
262
Фролов, Г. В. Фролов. Язык
Самоучитель
Комбинированные интерфейсы К а к мы у ж е г о в о р и л и , н е в о з м о ж н о с т ь м н о ж е с т в е н н о г о н а с л е д о в а н и я к л а с с о в в С# с успехом компенсируется наличием мощного механизма интерфейсов. Производный класс м о ж е т быть у н а с л е д о в а н т о л ь к о от о д н о г о б а з о в о г о класса, однако при э т о м он может реализовать произвольное количество интерфейсов. И н т е р ф е й с ы г р у п п и р у ю т о п и с а н и я н а б о р о в м е т о д о в , и м е ю щ и х схожее н а з н а ч е н и е и ф у н к ц и о н а л ь н о с т ь . П р и н е о б х о д и м о с т и м о ж н о к о м б и н и р о в а т ь и н т е р ф е й с ы , созда¬ вая н о в ы е и н т е р ф е й с ы на базе у ж е В л и с т и н г е 8.3 мы п р и в е л и и с х о д н ы й т е к с т п р о г р а м м ы , в к о т о р о й п р и м е н я ю т с я комбинированные интерфейсы. Листинг
8.3.
Файл
using System; n a m e s p a c e Combine interface
IChannel
void
interface
IVolume
void
interface
ITunning
interface
ITvControl
IChannel,
IVolume
on void interface
class
IRadioControl
TvSet
private private private
ITunning,
IVolume
ITvControl uint uint bool
Глава 8. Интерфейсы
volume;
263
public channel volume power
1; 10; false;
void power
true;
void power
false;
void channel int return
channel;
void
setLevel (uint
volume
volumeLevel)
volumeLevel;
int return
class
volume;
RadioSet
private private public
uint uint
IRadioControl channel;
RadioSet
channel volume
1;
void channel
264
А В. Фролов, Г. В. Фролов. Язык
Самоучитель
void 0)
int return
channel;
void
(uint
volume
volumeLevel)
volumeLevel;
uint
1
return
volume;
class static
void
args)
TvSet tv; tv new T v S e t ITvControl if
tv
as
ITvControl;
null)
else реализован
интерфейс
RadioSet radio; radio new IRadioControl if
Глава 8. Интерфейсы
radioControl null)
radio
as
265
else реализован
интерфейс
канал
канал
Console
громкость
ReadLine
З д е с ь , так же как IChannel, IVolume и г р о м к о с т ь ю звука: interface
громкость
и и
в п р е д ы д у щ е й п р о г р а м м е , мы о п р е д е л и л и и н т е р ф е й с ы I T u n n i n g , предназначенные для управления каналами
IChannel
void uint
interface
IVolume
void uint
interface
ITunning
uint Н е к о т о р ы е из этих и н т е р ф е й с о в ( I C h a n n e l и I V o l u m e ) п р и м е н я ю т с я для у п р а в ления телевизором, а некоторые ( I T u n n i n g и д л я у п р а в л е н и я радио¬ приемником. С целью у п р о щ е н и я программы мы попарно скомбинировали интерфейсы, образо вав ITvControl и IRadioControl: interface
ITvControl
IVolume
on оf
266
А. В Фролов, Г.
Фролов. Язык
Самоучитель
interface
IRadioControl
ITunning,
IVolume
П е р в ы й и з этих и н т е р ф е й с о в с и м е н е м I T v C o n t r o l о б ъ е д и н я е т и н т е р ф е й с ы I C h a n n e l и I V o l u m e , д о б а в л я я к ним еще д в а метода (on и o f f ) , п о з в о л я ю щ и е в к л ю ч а т ь и в ы к л ю ч а т ь т е л е в и з о р . Таким о б р а з о м , этот к о м б и н и р о в а н н ы й и н т е р ф е й с п р е д н а з н а ч е н для у п р а в л е н и я т е л е в и з о р о м . К о м б и н и р о в а н н ы й и н т е р ф е й с I R a d i o C o n t r o l п р е д с т а в л я е т собой ч и с т у ю ком б и н а ц и ю и н т е р ф е й с о в I T u n n i n g и I V o l u m e без к а к и х - л и б о д о б а в л е н и й . О н создан для у п р а в л е н и я р а д и о п р и е м н и к о м . Теперь при о б ъ я в л е н и и класса T v S e t нам д о с т а т о ч н о у к а з а т ь , что о н р е а л и з у е т к о м б и н и р о в а н н ы й и н т е р ф е й с I T v C o n t r o l , н е п е р е ч и с л я я с о с т а в л я ю щ и е его и н т е р фсйсы I C h a n n e l и I V o l u m e : c l a s s TvSet private private private
ITvControl uint uint bool
volume; power;
public channel volume power
1; 10; false;
void power
true;
void power
Так как комбинированный и н т е р ф е й с I T v C o n t r o l включает в себя м е т о д ы on и of f, нам н е о б х о д и м о реализовать их в классе Для хранения текущего с о стояния телевизора (включен или выключен) мы предусмотрели в э т о м классе поле Аналогично при объявлении класса R a d i o S e t мы указали, что он р е а л и з у е т к о м бинированный интерфейс class
RadioSet
private private
uint uint
IRadioControl
volume;
Глава 8. Интерфейсы
267
public channel volume
10;
Так как в р а м к а х этого и н т е р ф е й с а нет н и к а к и х д о п о л н и т е л ь н ы х м е т о д о в , отсутст¬ в у ю щ и х в с о с т а в л я ю щ и х его и н т е р ф е й с а х , при о б ъ я в л е н и и класса R a d i o S e t нам не пришлось объявлять дополнительные методы. Теперь мы р а с с к а ж е м о т о м , как п о л ь з о в а т ь с я к о м б и н и р о в а н н ы м и и н т е р ф е й с а м и . С о з д а в о б ъ е к т класса T v S e t , м ы о б ъ я в л я е м п е р е м е н н у ю t v C o n t r o l , предназна¬ ч е н н у ю для х р а н е н и я ссылки н а и н т е р ф е й с I T v C o n t r o l : TvSet tv; tv new T v S e t ITvControl tvControl
tv
as
ITvControl;
Эта переменная инициализируется безопасным способом с применением оператора a s . Если с с ы л к а н а и н т е р ф е й с I T v C o n t r o l п о л у ч е н а у с п е ш н о , м ы и с п о л ь з у е м для вызова м е т о д о в к о м б и н и р о в а н н о г о и н т е р ф е й с а : null)
else реализован
интерфейс
О б р а т и т е в н и м а н и е , что с п о м о щ ь ю е д и н с т в е н н о й ссылки на к о м б и н и р о в а н н ы й интерфейс I T v C o n t r o l м ы в ы з ы в а е м м е т о д ы в х о д я щ и х в него и н т е р ф е й с о в I C h a n n e l и I V o l u m e . Так как все эти м е т о д ы н а з ы в а ю т с я п о - р а з н о м у , в д а н н о м случае не в о з н и к а ю т к о н ф л и к т ы м е ж д у о д и н а к о в ы м и и м е н а м и м е т о д о в , щих разным интерфейсам. Аналогичным образом создается и используется п р е д н а з н а ч е н н ы й для у п р а в л е н и я р а д и о п р и е м н и к о м :
интерфейс
IRadioControl,
RadioSet radio new IRadioControl if
radioControl null)
radio
as
else реализован
268
интерфейс Фролов, Г. В. Фролов. Язык С#. Самоучитель
А теперь мы рассмотрим более интересный
случай, связанный
с вызовом
метода
c u r r e n t . Этот метод, возвращающий текущее значение номера канала или уровня гром¬ кости, определен во всех базовых интерфейсах, и м е ю щ и х с я в нашей программе. П о п ы т к и о б р а щ е н и я к этому методу ч е р е з ссылки current()
и
на и н т е р ф е й с ы вида
current
tvCon-
к п о я в л е н и ю н а этапе
к о м п и л я ц и и с о о б щ е н и я об о ш и б к е . Д е л о в т о м , что в такой записи не я с н о , о какой и м е н н о р е а л и з а ц и и метода c u r r e n t идет р е ч ь . В самом д е л е , к о м б и н и р о в а н н ы й и н т е р ф е й с I T v C o n t r o l состоит и з и н т е р ф е й с о в IChannel и
В к а ж д о м и з них имеется свой метод
комбинированный
интерфейс
состоящий
Аналогично
из интерфейсов
I
n i n g и I V o l u m e , тоже с о д е р ж и т в себе д в е р а з л и ч н ы е р е а л и з а ц и и м е т о д а c u r r e n t . Для того ч т о б ы и з б е ж а т ь н е о д н о з н а ч н о с т и , н е о б х о д и м о п р и в о д и т ь ссылку на ком¬ б и н и р о в а н н ы й и н т е р ф е й с к типу того и н т е р ф е й с а , для к о т о р о г о н у ж н о в ы з в а т ь наш метод: канал
громкость
Здесь м ы в н а ч а л е в ы з ы в а е м м е т о д c u r r e n t и н т е р ф е й с а I C h a n n e l ( п о л у ч а я при этом т е к у щ и й н о м е р к а н а л а ) , а затем этот же м е т о д , но д л я и н т е р ф е й с а
IVolume
(для о п р е д е л е н и я т е к у щ е г о у р о в н я г р о м к о с т и ) . Аналогичным образом
эта о п е р а ц и я
выполняется
и д л я интерфейса
C o n t r o l , управляющего радиоприемником: канал
громкость
Для того ч т о б ы и з б е ж а т ь в о з н и к н о в е н и я и с к л ю ч е н и й , для п р е о б р а з о в а н и я т и п о в вы можете воспользоваться описанными выше операторами is и a s .
Интерфейсы и наследование классов Если базовый класс р е а л и з у е т к а к и е - л и б о и н т е р ф е й с ы , то они н а с л е д у ю т с я производ¬ ными классами.
При необходимости производный
класс м о ж е т п е р е о п р е д е л и т ь все
или н е к о т о р ы е м е т о д ы и н т е р ф е й с о в б а з о в о г о класса. Р а с с м о т р и м простой п р и м е р . Пусть у нас есть базовый класс T v S e t , о п и с ы в а ю щ и й т е л е в и з о р . В этом классе мы п р е д у с м о т р е л и средства п е р е к л ю ч е н и я к а н а л о в и р е г у л и р о в к и г р о м к о с т и , реали¬ зованные с п о м о щ ь ю интерфейсов. Теперь
на
базе
класса
TvSet
нам
бы
хотелось
создать
новый
класс
в к о т о р о м ч е т н ы е к а н а л ы были бы р у с с к и м и , а лийскими. Глава 8. Интерфейсы
269
Исходный текст программы, объявляющей и использующей эти классы, мы приве ли в листинге 8.4. Листинг
8.4.
Файл
using System; namespace interface
IChannel
void uint
interface void uint
class
IVolume
current
TvSet
protected protected
IChannel,
IVolume
uint uint
public channel volume
1; -
void channel
int return
void
channel;
IVolume
setLevel (uint
volumeLevel)
volume
uint return
270
volume;
А В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
interface string class
TvSet,
private public
IChannel,
IVolume,
ILanguage
string
void channel 0) else "english";
string return
class static
void
args)
InternationalTvSet tv new
tv;
IChannel tv as I C h a n n e l ; IVolume tvVolume tv as ILanguage tvLanguage tv as I L a n g u a g e ; if ( t v C h a n n e l null tvLanguage
"Телевизор:
Интерфейсы
tvVolume
null
null)
канал
громкость
271
"Телевизор:
канал
громкость
else реализован IVolume
интерфейс
IChannel,
или
Для п е р е к л ю ч е н и я к а н а л о в и р е г у л и р о в к и г р о м к о с т и мы о п р е д е л и л и у ж е з н а к о м ы е вам и н т е р ф е й с ы I C h a n n e l и I V o l u m e : interface void uint
IChannel
switchTo (uint current
interface
channelNumber)
IVolume
void uint Эти и н т е р ф е й с ы р е а л и з о в а н ы в б а з о в о м классе T v S e t а н а л о г и ч н о т о м у , как это б ы л о сделано в п р е д ы д у щ е й п р о г р а м м е (см. л и с т и н г 8.3). Что ж е касается п р о и з в о д н о г о класса еще один и н т е р ф е й с
ILanguage,
то он реализует
п о з в о л я ю щ и й о п р е д е л и т ь н а ц и о н а л ь н ы й я з ы к те¬
к у щ е г о канала: Interface string При
ILanguage current
объявлении
производного
класса
вый класс T v S e t , а т а к ж е
InternationalTvSet
м ы у к а з а л и базо
все и н т е р ф е й с ы , р е а л и з у е м ы е в р а м к а х базо¬
вого и п р о и з в о д н о г о класса: InternationalTvSet private
272
TvSet,
IChannel,
IVolume,
ILanguage
string
А В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
public
void channel 0) else
string return
Конструктор класса I n t e r n a t i o n a l T v S e t устанавливает ПО у м о л ч а н и ю р у с ский язык,
инициализируя
о б р а з о м поле i n t e r f a c e L a n g u a g e .
В дальнейшем с о д е р ж и м о е э т о г о поля б у д е т зависеть от н о м е р а телевизором. В интерфейсе содержимого
поля
р е а л и з о в а н н о м базовым классом, нет средств и з м е н е н и я interf
aceLanguage,
классе переопределить м е т о д Новая
реализация
п о э т о м у нам придется в
производном
switchTo
этого
метода,
предусмотренная
в
производном
классе
I n t e r n a t i o n a l T v S e t , скрывает реализацию э т о г о ж е метода базовым классом. Дополнительно класс
I n t e r n a t i o n a l T v S e t р е а л и з у е т м е т о д c u r r e n t интер¬
фейса I L a n g u a g e , предназначенный для определения текущего языка. Теперь мы расскажем о т о м , как м е т о д M a i n нашей программы и с п о л ь з у е т о п и санные выше Прежде
всего
этот
метод
создает
объект
производного
класса
Interna
t i o n a l T v S e t и получает ссылки н а в с е н е о б х о д и м ы е интерфейсы: InternationalTvSet tv new
tv;
IChannel tvChannel tv as IVolume tvVolume tv as ILanguage tvLanguage tv as
ILanguage;
Если в с е э т и ссылки получены у с п е ш н о , программа продолжает р а б о т у , а в п р о тивном случае выводит на консоль с о о б щ е н и е об if ( t v C h a n n e l
null
8. Интерфейсы
tvVolume
null
tvLanguage
null)
273
else реализован
интерфейс
IChannel,
IVolume
или
В том с л у ч а е , если ссылки на все и н т е р ф е й с ы п о л у ч е н ы у с п е ш н о , п р о г р а м м а уста навливает
г р о м к о с т и 50
п е р е к л ю ч а е т т е л е в и з о р на 10-й к а н а л , а затем ото
бражает на к о н с о л и его с о с т о я н и е :
канал
громкость
Так как н о м е р 10-го канала ч е т н ы й , для него будет выбран р у с с к и й и н т е р ф е й с . На с л е д у ю щ е м этапе п р о г р а м м а п е р е к л ю ч а е т т е л е в и з о р на ( н е ч е т н ы й ) канал, повторяя в ы в о д с о с т о я н и я на к о н с о л ь : канал
громкость
О б р а т и т е в н и м а н и е , что здесь мы и с п о л ь з у е м без каких бы то ни было и з м е н е н и й и н т е р ф е й с I V o l u m e , р е а л и з о в а н н ы й в б а з о в о м классе T v S e t . Обращаясь к интерфейсу
I C h a n n e l , н а ш а п р о г р а м м а и м е е т д е л о с методами это
го и н т е р ф е й с а , р е а л и з о в а н н ы м и как в б а з о в о м , так и в п р о и з в о д н о м к л а с с е . Н а п р и м е р , метод c u r r e n t р е а л и з о в а н в базовом классе и не и з м е н я е т с я в п р о и з в о д н о м к л а с с е , а метод s w i t c h T o , н а п р о т и в , п е р е о п р е д е л е н в д о ч е р н е м классе. И н а к о н е ц , наша п р о г р а м м а и с п о л ь з у е т метод и н т е р ф е й с а I L a n g u a g e , реализо¬ ванный
в п р о и з в о д н о м классе
I n t e r n a t i o n a l T v S e t и никак не у п о м и н а ю щ и й с я
в б а з о в о м классе T v S e t . Таким о б р а з о м , п р о и з в о д н ы е классы м о г у т н а с л е д о в а т ь не т о л ь к о м е т о д ы , но и ин¬ т е р ф е й с ы б а з о в о г о класса, при н е о б х о д и м о с т и п е р е о п р е д е л я я их.
Свойства в интерфейсах В п р е д ы д у щ и х п р и м е р а х п р о г р а м м мы с о з д а в а л и и н т е р ф е й с ы , с о д е р ж а щ и е и с к л ю ч и т е л ь н о м е т о д ы . М е ж д у тем в р а м к а х и н т е р ф е й с о в С# д о п у с к а е т с я о б ъ я в л я т ь свойства, индексаторы и события. В этом р а з д е л е мы р а с с м о т р и м п р и м е р п р о г р а м м ы у п р а в л е н и я т е л е в и з о р о м , функ¬ ц и о н и р о в а н и е к о т о р о й о с н о в а н о на и с п о л ь з о в а н и и с в о й с т в , о б ъ я в л е н н ы х в рамках ин¬ т е р ф е й с а I T v C o n t r o l ( л и с т и н г 8.5). Листинг
8.5.
Файл
using System; namespace Properties interface
274
ITvControl
А В Фролов, Г. В. Фролов. Язык
Самоучитель
bool
PowerOn
byte
MaxChannel
get
byte
Channel
byte
Volume
class
TelevisionSet
private private private private
bool byte byte byte
ITvControl
isPowerOn; maxChannel;
public isPowerOn maxChannel
false;
currentVolume
10;
bool get return
isPowerOn;
set isPowerOn
Глава
Интерфейсы
275
byte get
byte get return J set if ( v a l u e
maxChannel value 0) currentChannel value;
byte get return set 0
value currentVolume
100) value;
else currentVolume
class
0;
PropertiesApp
static
void
args)
TelevisionSet TelevisionSet tvSmall tvLarge ITvControl ITvControl
tvSmall; tvLarge;
new T e l e v i s i o n S e t new tvsControl tvlControl
if ( t v s C o n t r o l
null
tvSmall tvLarge
as as
tvlControl реализован
ITvControl; ITvControl; null) интерфейс
ITvControl")
return;
276
А В Фролов, Г В. Фролов, Язык
true; 5 50; true; 27; 30; "Телевизор PowerOn
tvSmall: канал "Включен"
из
громкость "Выключен",
из
громкость "Выключен",
tvLarge: канал "Включен"
Р а н е е в этой главе мы у ж е п р о в о д и л и а н а л о г и ю м е ж д у и н т е р ф е й с а м и С# и абст р а к т н ы м и к л а с с а м и . В гл. 6 мы р а с с к а з ы в а л и про а б с т р а к т н ы е и н т е р ф е й с ы , о б ъ я в л е ние к о т о р ы х н е с о д е р ж и т тела ф у н к ц и й д о с т у п а и set. А н а л о г и ч н ы м о б р а з о м о б ъ я в л я ю т с я с в о й с т в а в р а м к а х и н т е р ф е й с а . Н и ж е мы п р и вели и с х о д н ы й текст и н т е р ф е й с а I T v C o n t r o l , с о д е р ж а щ е г о о б ъ я в л е н и я с в о й с т в , с помощью которых можно управлять нашим виртуальным телевизором: interface bool
ITvControl PowerOn
get;
byte
MaxChannel
byte
Channel
Глава 8. Интерфейсы
277
byte
Volume
Свойство P o w e r O n п о з в о л я е т в к л ю ч а т ь или в ы к л ю ч а т ь т е л е в и з о р п о с р е д с т в о м за писи в него з н а ч е н и й t r u e или
s
Это же с в о й с т в о м о ж е т быть
и с п о л ь з о в а н о для о п р е д е л е н и я с о с т о я н и я , в к о т о р о м н а х о д и т с я т е л е в и з о р ,
вклю¬
ч е н н о е или в ы к л ю ч е н н о е . С п о м о щ ь ю с в о й с т в а M a x C h a n n e l м о ж н о о п р е д е л и т ь м а к с и м а л ь н ы й номер кана¬ ла, п р и н и м а е м о г о т е л е в и з о р о м .
Это с в о й с т в о д о с т у п н о т о л ь к о для ч т е н и я , так как
в нем п р е д у с м о т р е н а только одна ф у н к ц и я д о с т у п а g e t . Свойство
Channel
позволяет
т е л е в и з о р н а л ю б о й з а д а н н ы й канал,
а также о п р е д е л я т ь н о м е р т е к у щ е г о канала. И наконец, п о с р е д с т в о м свойства V o l u m e м о ж н о р е г у л и р о в а т ь у р о в е н ь г р о м к о с т и , а т а к ж е о п р е д е л я т ь т е к у щ и й уровень г р о м к о с т и . При о б ъ я в л е н и и класса T e l e v i s i o n S e t м ы у к а з а л и , что о н р е а л и з у е т только что описанный
интерфейс
class
ITvControl: ITvControl
private private private private
bool byte byte byte
isPowerOn; maxChannel;
public
currentVolume
О б р а т и т е в н и м а н и е , что при р е а л и з а ц и и и н т е р ф е й с а мы не у к а з ы в а е м модифика¬ тор д о с т у п а , а имя с в о й с т в а задаем вместе с и м е н е м и н т е р ф е й с а : byte get return
278
А В
Г. В. Фролов. Язык
Самоучитель
if ( v a l u e
maxChannel value value;
0)
Р е а л и з а ц и я п р о ц е д у р д о с т у п а о с о б е н н о с т е й не имеет. Она была о п и с а н а в гл. 6, по¬ этому вы с м о ж е т е р а з о б р а т ь с я в этом с а м о с т о я т е л ь н о .
Индексаторы в интерфейсах Если класс создан для п р е д с т а в л е н и я о б ъ е к т а с м а с с и в о м , для д о с т у п а к нему м о ж н о и с п о л ь з о в а т ь и н д е к с а т о р ы . Об этом мы р а с с к а з ы в а л и вам в п р е д ы д у щ е й г л а в е . К а к мы уже
говорили,
индексатор
м о ж е т быть
включен
в
о б ъ я в л е н и е и н т е р ф е й с а наряду
с методами и другими членами. В л и с т и н г е 8.6 мы п р и в е л и м о д и ф и ц и р о в а н н ы й в а р и а н т п р о г р а м м ы , р а с с м о т р е н ной нами р а н е е в разделе « И н д е к с а т о р ы » п р е д ы д у щ е й г л а в ы . Эта п р о г р а м м а исполь¬ зует к л а с с , х р а н я щ и й н а з в а н и я т е л е в и з и о н н ы х к а н а л о в в м а с с и в е . 8.6.
Файл
using System; namespace interface uint
IChannelNames Size
string
class
this[uint
ChannelNames
private private public
uint
index]
IChannelNames
ArraySize;
ChannelNames ( u i n t
Channels ArraySize
Глава 8. Интерфейсы
Count)
new Count;
279
uint get return
ArraySize;
string
index]
get 0 return
index
else return
"Канал
set 0
index value;
class
IndexerApp
static
void
args)
C h a n n e l N a m e s ch IChannelNames
new
ChannelNames ch as
null) реализован
интерфейс
"Мир "Боевик"; "Наше к и н о " ; i
0;
i
i++)
s
280
А. В. Фролов,
В.
Для терфейс
д о с т у п а к э л е м е н т а м массива названий каналов мы о б ъ я в и л и ин
interface uint
IChannelNames Size
string
index]
Этот и н т е р ф е й с в к л ю ч а е т в себя с в о й с т в о S i z e , а т а к ж е и н д е к с а т о р . Свойство S i z e позволяет узнать текущий размер массива, созданного конструкто р о м , п о э т о м у оно и м е е т т о л ь к о одну п р о ц е д у р у д о с т у п а g e t . Ч т о ж е касается и н д е к сатора, т о м ы о б ъ я в и л и д л я него обе п р о ц е д у р ы и set. Таким о б р а з о м , через и н т е р ф е й с I C h a n n e l N a m e s п р о г р а м м а с м о ж е т о п р е д е л и т ь размер м а с с и в а , а т а к ж е п о л у ч и т ь или и з м е н и т ь с о д е р ж и м о е его о т д е л ь н ы х я ч е е к . П р и о б ъ я в л е н и и класса C h a n n e l N a m e s м ы у к а з а л и , что о н р е а л и з у е т и н т е р ф е й с IChannelNames: class
ChannelNames
private private public
IChannelNames
string[] uint
Channels;
ChannelNames ( u i n t
Channels ArraySize
Count)
new s t r i n g [ C o u n t ] Count;
К о н с т р у к т о р этого класса о б е с п е ч и в а е т с о з д а н и е м а с с и в а н е о б х о д и м ы х р а з м е р о в , а т а к ж е и н и ц и а л и з а ц и ю поля х р а н я щ е г о размер массива. Реализация свойства S i z e не имеет никаких особенностей: uint get return
ArraySize;
Здесь и с п о л ь з у е т с я п о л н о е имя с в о й с т в а , в к л ю ч а ю щ е е имя и н т е р ф е й с а ; модифика¬ торы д о с т у п а не у к а з ы в а ю т с я . 8. Интерфейсы
281
Что же касается и н д е к с а т о р а , то он р е а л и з о в а н с л е д у ю щ и м о б р а з о м : string
index]
get 0 return else return
"Канал
index
недоступен";
set 0
index value;
Мы д о б а в и л и к и м е н и и н д е к с а т о р а н а з в а н и е и н т е р ф е й с а , в р е з у л ь т а т е чего получи¬ ли полное имя I C h a n n e l N a m e s В о с т а л ь н о м этот и н д е к с а т о р п о л н о с т ь ю ана л о г и ч е н и н д е к с а т о р у , о п и с а н н о м у в п р е д ы д у щ е й главе (см. л и с т и н г 7.7). Теперь мы р а с с к а ж е м о т о м , как п о л ь з о в а т ь с я с о з д а н н ы м нами и н т е р ф е й с о м . В н а ч а л е н е о б х о д и м о создать о б ъ е к т ( м а с с и в названий к а н а л о в ) , а т а к ж е ссылку на и н т е р ф е й с : C h a n n e l N a m e s ch new I C h a n n e l N a m e s chNames
ch
as
С о з д а н н а я с с ы л к а затем п р о в е р я е т с я н а р а в е н с т в о з н а ч е н и ю n u l l : if (chNames
null) реализован
интерфейс
Если с с ы л к а на и н т е р ф е й с была п о л у ч е н а у с п е ш н о , мы и н и ц и а л и з и р у е м 5 ячеек массива, используя ссылку на интерфейс I C h a n n e l N a m e s : "Мир "Боевик"; "Наше к и н о " ; Как в и д и т е , а д р е с а ц и я я ч е е к м а с с и в а через и н д е к с а т о р , о б ъ я в л е н н ы й в рамках ин¬ терфейса, выполняется очевидным и ожидаемым образом. Чтение ячеек массива происходит аналогично: i string
i
i++)
s
П е р е б и р а я я ч е й к и , мы п о л у ч а е м о ч е р е д н у ю с т р о к у н а з в а н и я канала и в ы в о д и м ее на к о н с о л ь .
282
Фролов, Г. В. Фролов.
Глава 9. Обработка исключений О б щ е и з в е с т н о м н е н и е о т о м , что ч е л о в е к м о ж е т о ш и б а т ь с я , а к о м п ь ю т е р ы нет. На в е р н о е , только п р о ф е с с и о н а л ь н ы е п р о г р а м м и с т ы и администраторы знают, н а с к о л ь к о вторая часть этого не с о о т в е т с т в у е т д е й с т в и т е л ь н о с т и . К а к о в а же п р и ч и н а п о я в л е н и я к о м п ь ю т е р н ы х о ш и б о к и м о ж н о ли п р е д о т в р а т и т ь их п о я в л е н и е ? На наш взгляд, б о л ь ш и н с т в о таких о ш и б о к связано с тем, что к о м п ь ю т е р ы и про¬ г р а м м ы для них создаются л ю д ь м и . С о в р е м е н н ы е к о м п ь ю т е р ы и п р о г р а м м ы чрезвы¬ чайно с л о ж н ы , и ч е л о в е к просто не в с о с т о я н и и у д е р ж а т ь в голове все д е т а л и и осо¬ бенности их р а б о т ы . Проблемы начинаются уже в центральном процессоре, содержащем миллионы к о м п о н е н т о в . Когда п о я в и л а с ь первая о ш и б к а в п р о ц е с с о р е P e n t i u m к о м п а н и и Intel, это в ы з в а л о ш о к о в у ю р е а к ц и ю у многих п о л ь з о в а т е л е й к о м п ь ю т е р о в , после чего на¬ чалась м а с с о в а я з а м е н а д е ф е к т н ы х п р о ц е с с о р о в . Мы тоже в свое время были с ч а с т л и выми о б л а д а т е л я м и д е ф е к т н о г о п р о ц е с с о р а P e n t i u m 90 и, п о л ь з у я с ь с л у ч а е м , д а ж е на¬ писали пару статей на эту тему. С о в р е м е н н ы е п р о ц е с с о р ы д о п у с к а ю т и с п р а в л е н и е не¬ к о т о р ы х о ш и б о к п р о г р а м м н ы м с п о с о б о м , для чего в к о м п а н и и Intel была р а з р а б о т а н а специальная технология. Н а д о сказать, что о ш и б к и в п р о ц е с с о р а х п о я в л я ю т с я вовсе не п о т о м у , что разра¬ ботчик у д е л я е т н е д о с т а т о ч н о в н и м а н и я т е с т и р о в а н и ю н о в ы х м о д е л е й , стремясь вы¬ пустить их на р ы н о к как м о ж н о быстрее (хотя какая-то д о л я п р а в д ы тут есть). На тес¬ т и р о в а н и е и п о и с к о ш и б о к т р а т я т с я о г р о м н ы е л ю д с к и е и ф и н а н с о в ы е р е с у р с ы , однако из-за н е в о о б р а з и м о й с л о ж н о с т и п р о ц е с с о р о в н е р е а л ь н о провести полное т е с т и р о в а н и е всех его к о м п о н е н т о в за р а з у м н о е в р е м я . С о в р е м е н н ы е ОС и с л о ж н ы е п р о г р а м м ы т а к ж е и з о б и л у ю т о ш и б к а м и , н е к о т о р ы е из к о т о р ы х м е ш а ю т п о л ь з о в а т е л я м , а н е к о т о р ы е так и о с т а ю т с я н е з а м е ч е н н ы м и . Не з а т и х а ю т с п о р ы о т о м , какая ОС р а б о т а е т н а д е ж н е е Microsoft W i n d o w s или L i n u x . Мы п о л а г а е м , что все с у щ е с т в у ю щ и е версии этих и д р у г и х ОС н е н а д е ж н ы по п р и ч и н е п р и с у т с т в и я в них о ш и б о к . В з а в и с и м о с т и от с и т у а ц и и ( к о н ф и г у р а ц и и а п п а р а т н о г о о б е с п е ч е н и я , д р а й в е р о в , н а б о р а у с т а н о в л е н н ы х п р о г р а м м и т. п.) эта н е н а д е ж н о с т ь м о ж е т с к а з ы в а т ь с я в б о л ь ш е й или м е н ь ш е й степени. Е щ е одна важная п р и ч и н а в о з н и к н о в е н и я о ш и б о к при р а б о т е п р о г р а м м н о г о о б е с печения отказы а п п а р а т у р ы к о м п ь ю т е р а . Н и ч т о не вечно под Л у н о й , и к о м п ь ю т е р тоже не я в л я е т с я и с к л ю ч е н и е м . И з н о с у п о д в е р ж е н ы в п е р в у ю о ч е р е д ь такие компо¬ н е н т ы , как н а к о п и т е л и д а н н ы х на д и с к а х , х о т я о т к а з ы в а ю т и д р у г и е к о м п о н е н т ы . П о с л е д с т в и я к о м п ь ю т е р н ы х о ш и б о к м о г у т быть очень т я ж е л ы м и . В р е з у л ь т а т е их п о я в л е н и я могут пропасть очень в а ж н ы е с т о и м о с т ь к о т о р ы х п о р о й много¬ кратно п р е в ы ш а е т с т о и м о с т ь всей к о м п ь ю т е р н о й с и с т е м ы . И с к а ж е н и е и н ф о р м а ц и и о пациентах в м е д и ц и н с к о й базе д а н н ы х м о ж е т д а ж е п р и в е с т и к т р а г и ч е с к о м у исходу. Как же з а с т р а х о в а т ь себя от п о я в л е н и я о ш и б о к или по крайней сократить у щ е р б от их в о з н и к н о в е н и я ?
283
и с и с т е м н ы е а д м и н и с т р а т о р ы д о л ж н ы о т в е т с т в е н н о относиться к в ы б о р у и н а с т р о й к е п р о г р а м м н о г о о б е с п е ч е н и я к о м п ь ю т е р н о й с и с т е м ы , к резервно¬ му к о п и р о в а н и ю д а н н ы х и а н т и в и р у с н о й з а щ и т е . На сайте с л у ж б ы в о с с т а н о в л е н и я данных вы н а й д е т е м н о г о ч и с л е н н ы е ма¬ т е р и а л ы , к о т о р ы е п о м о г у т вам и з б е ж а т ь потерь д а н н ы х или с н и з и т ь у щ е р б , если дан¬ ные уже п р о п а л и . Что же касается п р о г р а м м и с т о в , р а з р а б а т ы в а ю щ и х н о в ы е п р и л о ж е н и я , то они д о л ж н ы с а м ы м в н и м а т е л ь н ы м о б р а з о м о б р а б а т ы в а т ь о ш и б о ч н ы е с и т у а ц и и , возни¬ к а ю щ и е при работе этих п р и л о ж е н и й . И хотя едва ли в о з м о ж н о п о л н о с т ь ю избавиться от к о м п ь ю т е р н ы х о ш и б о к , п о с т а р а т ь с я не у в е л и ч и в а т ь с и л ь н о их к о л и ч е с т в о в н о в ы х р а з р а б о т к а х , п о л а г а е м , все же стоит.
Классические способы обработки ошибок О т в л е к а я с ь о т ф и л о с о ф с к и х п р и ч и н в о з н и к н о в е н и я к о м п ь ю т е р н ы х о ш и б о к , рассмот¬ р и м с и т у а ц и и , в к о т о р ы х такие о ш и б к и о б ы ч н о в о з н и к а ю т при с о з д а н и и п р о г р а м м . П р о с т е й ш и е п р и м е р ы — д е л е н и е на нуль и в ы х о д за г р а н и ц ы массива. Рассмотрим программу, исходный текст которой приведен в листинге Листинг
9.1.
Файл
namespace class
DivideByZeroApp
static
void
int
i
args) 0;
intj
Ошибка
деления
на
Мы о б ъ я в и л и в ней две п е р е м е н н ы е т и п а i n t с и м е н а м и i и j. П е р е м е н н о й i при¬ с в а и в а е т с я н у л е в о е з н а ч е н и е , после чего п р е д п р и н и м а е т с я п о п ы т к а р а з д е л и т ь число 5 н а с о д е р ж и м о е i , записав р е з у л ь т а т в п е р е м е н н у ю j . К о м п и л я т о р не с л е д и т за с о д е р ж и м ы м п е р е м е н н ы х , к о т о р о е они п о л у ч а т в процес¬ се в ы п о л н е н и я п р о г р а м м ы , п о э т о м у он не с м о ж е т п р е д у г а д а т ь , что будет п р е д п р и н я т а п о п ы т к а д е л е н и я числа 5 на 0. В р е з у л ь т а т е к о м п и л я ц и я з а к о н ч и т с я у с п е ш н о . О д н а к о при п о п ы т к е з а п у с т и т ь п р о г р а м м у на экране п о я в и т с я с л е д у ю щ е е сообще¬ ние об о ш и б к е : An unhandled occurred in Additional 284
exception
information:
of
type
Attempted
to
divide
by
zero.
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
В этом произошло программе никло при
с о о б щ е н и и г о в о р и т с я , что при в ы п о л н е н и и исключение не был п р е д у с м о т р е н о б р а б о т ч и к . п о п ы т к е в ы п о л н и т ь д е л е н и е на нуль.
программы DivideByZero.exe для к о т о р о г о в н а ш е й т а к ж е , что и с к л ю ч е н и е воз¬
Н а п о м н и м , что п р о г р а м м ы С# в ы п о л н я ю т с я под у п р а в л е н и е м с и с т е м ы Microsoft F r a m e w o r k , к о н т р о л и р у ю щ е й п о я в л е н и е о ш и б о к п о д о б н о г о рода. Если в прог¬ р а м м е н е п р е д у с м о т р е т ь о б р а б о т к у о ш и б о ч н о й с и т у а ц и и , т о при п о я в л е н и и о ш и б к и система Microsoft F r a m e w o r k просто з а в е р ш и т ее работу с выдачей на экран со¬ ответствующего сообщения. А н а л о г и ч н ы м о б р а з о м ведут себя п р о г р а м м ы , с о с т а в л е н н ы е на д р у г и х я з ы к а х про¬ г р а м м и р о в а н и я . П р и этом о т в е т с т в е н н о с т ь з а а в а р и й н о е п р е к р а щ е н и е р а б о т ы с б о й н о й п р о г р а м м ы л е ж и т на О С . Если она не с п р а в и т с я с этой задачей д о л ж н ы м о б р а з о м , мо¬ гут в о з н и к н у т ь с е р ь е з н ы е п р о б л е м ы , вплоть до «зависания» к о м п ь ю т е р а и п о т е р и данных.
Предварительная проверка параметров Ваша программа не должна выдавать пользователю системное сообщение об ошибке, аналогичное приведенному выше. Обнаружив ошибку, программа должна сообщить пользователю причины возникновения ошибки, а также рекомендовать какие-либо д е й с т в и я . В с е это н е в о з м о ж н о без создания в п р о г р а м м е с о б с т в е н н о й с и с т е м ы обра¬ ботки о ш и б о к . Н и ж е м ы п р е д с т а в и л и п р о с т е й ш и й способ п р е д о т в р а щ е н и я о ш и б к и д е л е н и я на нуль ( з а м е т ь т е : именно п р е д о т в р а щ е н и я , а не о б р а б о т к и ) : if(i
0)
int j
5
i;
else деления
на
Если д е л и т е л ь равен н у л ю , то о п е р а ц и я д е л е н и я не в ы п о л н я е т с я . В м е с т о этого на к о н с о л ь в ы в о д и т с я с о о б щ е н и е об о ш и б к е . М н о г о о ш и б о к в ы з ы в а е т с я т е м , что ф у н к ц и я м или м е т о д а м п е р е д а ю т с я неправиль¬ ные з н а ч е н и я п а р а м е т р о в . Ч т о б ы п р е д о т в р а т и т ь в о з н и к н о в е н и е т а к и х о ш и б о к , ваша п р о г р а м м а д о л ж н а перед в ы з о в о м функций или м е т о д о в п р о в е р я т ь з н а ч е н и я парамет¬ ров на д о п у с т и м о с т ь . О д н а к о в р е а л ь н ы х п р о г р а м м а х такие п р о в е р к и часто опускают¬ ся, что и п р и в о д и т к п о я в л е н и ю н е о ж и д а н н ы х и т р у д н о о б н а р у ж и в а е м ы х о ш и б о к .
Проверка кодов возврата функций и методов Р а с с м о т р и м теперь д р у г о й случай ( л и с т и н г 9.2). Здесь мы в ы з ы в а е м в ц и к л е м е т о д D i v i d e , в ы п о л н я ю щ и й д е л е н и е п е р в о г о своего п а р а м е т р а н а в т о р о й . В к а ч е с т в е де лителя и с п о л ь з у е т с я п е р е м е н н а я цикла i , к о т о р а я в о время р а б о т ы ц и к л а п р и н и м а е т нулевое
значение.
Поэтому
при
работе
программы
происходит
ошибка
деления
на нуль. 9. Обработка
285
Листинг
9.2.
Файл
using System; namespace class
DivideByZerolApp
static
int
Divide(int x,
int
y)
int result; result x y; return result;
static
void
int j for(int j
i
args)
-3;
i
3;
Divide
i+ + ) Ошибка д е л е н и я
на
К а к и с к л ю ч и т ь п о я в л е н и е этой о ш и б к и ? М ы могли б ы н е м н о г о и з м е н и т ь м е т о д
D i v i d e , ч т о б ы о н п р о в е р я л з н а ч е н и е де¬
л и т е л я перед в ы п о л н е н и е м о п е р а ц и и д е л е н и я : int
int
Divide(int х,
int
у)
result;
if(y result return
0) x result;
else return Т е п е р ь о ш и б к а д е л е н и я на нуль не в о з н и к н е т , так как, если д е л и т е л ь равен н у л ю , д е л е н и е н е в ы п о л н я е т с я . О д н а к о м е т о д D i v i d e д о л ж е н к а к и м - т о о б р а з о м просигна¬ лизировать вызывающему методу о возникновении ошибки. Н а ш а р е а л и з а ц и я этого м е т о д а при о ш и б к е в о з в р а щ а е т н у л е в о е з н а ч е н и е , о д н а к о о ч е в и д н о , это не л у ч ш и й с п о с о б . В с а м о м д е л е , н у л е в о е з н а ч е н и е в о з в р а щ а е т с я и в том с л у ч а е , если д е л и т е л ь равен н у л ю , а эта с и т у а ц и я не я в л я е т с я о ш и б о ч н о й . З а м е т и м , что о с о б о г о з н а ч е н и я м е т о д о м или ф у н к ц и е й и с п о л ь з у е т с я очень часто в д р у г и х я з ы к а х п р о г р а м м и р о в а н и я в качестве п р и з н а к а о ш и б к и . Естест286
Г.
Фролов.
Самоучитель
венный п р о г р а м м н ы й и н т е р ф е й с (Application P r o g r a m Interface, A P I ) О С на базе набора ф у н к ц и й , в о з в р а щ а ю щ и х в с л у ч а е в о з н и к н о в е н и я о ш и б к и к а к о е - л и б о особое значение. П р е д п о л а г а е т с я , что после о б р а щ е н и я к ф у н к ц и и о п е р а ц и о н н о й с и с т е м ы ( т а к о й , н а п р и м е р , как о т к р ы т и е файла, ч т е н и е блока д а н н ы х из этого файла и т. п.) вызываю¬ щая п р о г р а м м а п р о в е р я е т код возврата, п р е д п р и н и м а я при в о з н и к н о в е н и и о ш и б к и ка¬ кие-либо действия. В о т как м ы м о ж е м и с п о л ь з о в а т ь м о д и ф и ц и р о в а н н ы й в а р и а н т м е т о д а D i v i d e , воз¬ в р а щ а ю щ и й н у л е в о е значение при в о з н и к н о в е н и и о ш и б к и д е л е н и я н а нуль: j
Divide (10,
i)
0) деления
на
В реальных программах встречается довольно большая глубина вложенности ф у н к ц и й и м е т о д о в , когда один метод в ы з ы в а е т д р у г о й , тот, в свою о ч е р е д ь , обраща¬ ется к т р е т ь е м у и ч е т в е р т о м у и т. д. С т р о к и л и с т и н г а п р о г р а м м ы , п р е д н а з н а ч е н н ы е для о б р а б о т к и к о д о в в о з в р а т а д л я всех этих ф у н к ц и й или м е т о д о в , з а г р о м о ж д а ю т и с х о д н ы й т е к с т п р о г р а м м ы , д е л а я его «нечитаемым». Кроме того, программист может по забывчивости опустить проверку кода в о з в р а т а к а к о й - л и б о о д н о й ф у н к ц и и или метода, что п р и в е д е т к а в а р и й н о м у за¬ в е р ш е н и ю п р и л о ж е н и я во в р е м я его р а б о т ы . Есть и еще одна п р о б л е м а . О б ы ч н о ф у н к ц и я , о б н а р у ж и в ш а я о ш и б к у , не знает, что с ней н у ж н о д е л а т ь . Дейст¬ вия по о б р а б о т к е о ш и б к и д о л ж н ы в ы б и р а т ь с я и с х о д я из к о н т е к с т а в ы з о в а этой функ¬ ц и и , так как одно и то же с о б ы т и е в р а з н ы х случаях м о ж е т и н т е р п р е т и р о в а т ь с я поразному. Рассмотрим следующую ситуацию. П у с т ь мы с к а н и р у е м д и с к , читая по о ч е р е д и все его с е к т о р ы до д о с т и ж е н и я к о н ц а д и с к а . Мы в ы п о л н я е м эту о п е р а ц и ю с ц е л ь ю о п р е д е л е н и я о б щ е г о к о л и ч е с т в а секто¬ р о в , р а с п о л о ж е н н ы х на д и с к е . Д а л е е , пусть у нас есть ф у н к ц и я R e a d S e c t o r , единст¬ венная задача к о т о р о й — чтение сектора д и с к а с з а д а н н ы м н о м е р о м . К о г д а в п р о ц е с с е п о с л е д о в а т е л ь н о г о чтения п р о г р а м м а д о б е р е т с я до конца д и с к а , в о з н и к н е т о ш и б к а , так как ф у н к ц и я R e a d S e c t o r п о п ы т а е т с я п р о ч и т а т ь сектор с не¬ с у щ е с т в у ю щ и м н о м е р о м . О ш и б к а м о ж е т в о з н и к н у т ь и в д р у г о й с и т у а ц и и — если су¬ щ е с т в у ю щ и й сектор д и с к а о к а ж е т с я и с п о р ч е н н ы м . С а м а по себе н а ш а ф у н к ц и я R e a d S e c t o r не знает, что делать с о ш и б к о й , так как она м о ж е т в ы з ы в а т ь с я и з р а з н ы х п р о г р а м м . П р и о б н а р у ж е н и и о ш и б к и в ы з ы в а ю щ а я п р о г р а м м а м о ж е т , н а п р и м е р , п о в т о р и т ь в ы п о л н е н и е о п е р а ц и и в н а д е ж д е на т о , что сектор все же будет прочитан п р а в и л ь н о , а м о ж е т а в а р и й н о з а в е р ш и т ь работу про¬ г р а м м ы . В о п и с а н н о м в ы ш е случае, когда п р о г р а м м а о п р е д е л я е т о б ъ е м д и с к а , возник¬ н о в е н и е о ш и б к и чтения сектора — н о р м а л ь н о е о ж и д а е м о е с о б ы т и е , к о т о р о е не долж¬ но р а с с м а т р и в а т ь с я как о ш и б о ч н о е . Глава 9. Обработка исключений
287
Таким о б р а з о м , о б ы ч н о р е а к ц и я на о ш и б к у в ы б и р а е т с я не той ф у н к ц и е й , которая о б н а р у ж и л а о ш и б к у , а в ы з ы в а ю щ е й п р о г р а м м о й . В случае м н о г о ч и с л е н н ы х вложен¬ ных в ы з о в о в ф у н к ц и й о ш и б к а м о ж е т о б р а б а т ы в а т ь с я на том или и н о м у р о в н е вложен¬ ности, з а р а н е е о б ы ч н о т р у д н о о п р е д е л и т ь , н а каком у р о в н е и м е н н о . В р е з у л ь т а т е анализ к о д о в возврата ф у н к ц и й п р е в р а щ а е т с я в н е п р о с т у ю задачу. П о н е в н и м а т е л ь н о с т и п р о г р а м м и с т м о ж е т п р о п у с т и т ь п р о в е р к у кода возврата какойлибо функции, в чего п р о г р а м м а станет работать с о ш и б к а м и .
Применение механизма исключений Как в и д и т е , к л а с с и ч е с к и е с х е м ы о б р а б о т к и о ш и б о к , о с н о в а н н ы е н а п р о в е р к е допус¬ т и м о с т и п а р а м е т р о в и анализе к о д о в возврата ф у н к ц и й и м е т о д о в , о б л а д а ю т сущест¬ венными недостатками. П р а к т и ч е с к и все с о в р е м е н н ы е языки п р о г р а м м и р о в а н и я с н а б ж е н ы м о щ н ы м сред¬ ством о б р а б о т к и о ш и б о к , о с н о в а н н ы м на и с п о л ь з о в а н и и так н а з ы в а е м ы х исключений (exceptions). я в л я е т с я и с к л ю ч е н и е м ( п р о с т и т е за т а в т о л о г и ю ) и я з ы к п р о г р а м м и р о вания С#. С р е д с т в а о б р а б о т к и и с к л ю ч е н и й в я з ы к е С# д е л а ю т и с х о д н ы й текст п р о г р а м м по¬ нятнее и п р о щ е . С их п о м о щ ь ю п р о г р а м м и с т м о ж е т о р г а н и з о в а т ь с т р у к т у р н у ю обра¬ ботку о ш и б о к , как п р о г н о з и р у е м ы х , так и в о з н и к а ю щ и х н е о ж и д а н н о . С и с т е м а о б р а б о т к и о ш и б о к , и с п о л ь з о в а н н а я в б и б л и о т е к е классов Microsoft Framework, работает исключительно с применением механизма исключений. Поэтому, к а к у ю бы п р о г р а м м у вы ни р а з р а б а т ы в а л и на я з ы к е С # , вы о б я з а т е л ь н о с т о л к н е т е с ь с н е о б х о д и м о с т ь ю о б р а б а т ы в а т ь или в ы з ы в а т ь и с к л ю ч е н и я (в д р у г и х я з ы к а х програм¬ м и р о в а н и я это не так; п р о г р а м м ы С + + , н а п р и м е р , м о ж н о с о з д а в а т ь и без о б р а б о т к и исключений).
Блоки try-catch Для того ч т о б ы о р г а н и з о в а т ь в своей п р о г р а м м е С# о б р а б о т к у о ш и б о к с использова¬ н и е м и с к л ю ч е н и й , н у ж н о п р и м е н и т ь б л о к и , с о з д а н н ы е при п о м о щ и к л ю ч е в ы х слов try,
catch и
С о з д а н и е и с к л ю ч е н и я (или, как еще г о в о р я т , передачу или
в о з б у ж д е н и е и с к л ю ч е н и я ) в ы п о л н я ю т с п о м о щ ь ю к л ю ч е в о г о слова t h r o w . В
блок
try
заключается
т. е. « н е н а д е ж н ы й » код.
код,
который
может
вызвать
возникновение
ошибок,
С л е д о м за б л о к о м t r y м о ж е т р а с п о л а г а т ь с я один или не
с к о л ь к о б л о к о в c a t c h , в к о т о р ы х н а х о д и т с я код для о б р а б о т к и о ш и б о к . Р а с с м о т р и м п р и м е р о б р а б о т к и о ш и б к и д е л е н и я на нуль с п о м о щ ь ю и с к л ю ч е н и й (листинг Листинг
9.3.
Файл
using namespace
class
288
DivideByZeroExApp
А. В. Фролов, Г. В. Фролов.
С#. Самоучитель
static int
void i
args) 0;
i n t j
5
i ex)
Ошибка в п р о г р а м м е
Здесь м ы з а к л ю ч и л и в блок t r y строку п р о г р а м м ы , при в ы п о л н е н и и к о т о р о й м о жет п р о и з о й т и о ш и б к а д е л е н и я на нуль: try j
i;
В блоке t r y м о ж е т р а с п о л а г а т ь с я п р о и з в о л ь н о е к о л и ч е с т в о п р о г р а м м н ы х строк, содержащих вызовы методов, обращения к интерфейсам, свойствам и индексаторам, о п е р а т о р ы цикла и т. п. При о т с у т с т в и и о ш и б о к все эти строки и с п о л н я ю т с я о б ы ч н ы м образом. Если же в п р о ц е с с е в ы п о л н е н и я к а к о й - л и б о п р о г р а м м н о й строки в о з н и к а е т ошиб¬ ка, н о р м а л ь н а я п о с л е д о в а т е л ь н о с т ь р а б о т ы п р о г р а м м ы п р е р ы в а е т с я и у п р а в л е н и е пе¬ р е д а е т с я б л и ж а й ш е м у блоку c a t c h , р а с п о л о ж е н н о м у вслед з а б л о к о м t r y . В о т как в нашей п р о г р а м м е в ы г л я д и т блок c a t c h , п р е д н а з н а ч е н н ы й для о б р а б о т к и о ш и б к и д е л е н и я на нуль: ex) Ошибка
в
программе
(0)
В к р у г л ы х скобках после к л ю ч е в о г о слова c a t c h у к а з ы в а е т с я тип ( к л а с с ) обраба¬ т ы в а е м о г о и с к л ю ч е н и я . В д а н н о м случае м ы у к а з а л и базовый класс S y s t e m . Ex c e p t i o n , от которого все о с т а л ь н ы е классы о б р а б о т к и и с к л ю ч е н и й . С к о н с т р у и р о в а н н ы й т а к и м с п о с о б о м о б р а б о т ч и к и с к л ю ч е н и й способен п е р е х в а т ы в а т ь и с к л ю ч е н и я всех т и п о в , в о з н и к а ю щ и х при в ы п о л н е н и и кода, о г р а н и ч е н н о г о предше¬ с т в у ю щ и м блоком t r y .
Глава 9. Обработка исключений 10
Язык С#
289
В с л е д за о б о з н а ч е н и е м класса о б р а б а т ы в а е м о г о и с к л ю ч е н и я мы указали имя менной
К о г д а блок
п о л у ч а е т у п р а в л е н и е , эта п е р е м е н н а я с о д е р ж и т ссылку
н а объект класса
С в о й с т в а этого класса с о д е р ж а т и н ф о р м а ц и ю ,
которая м о ж е т о к а з а т ь с я п о л е з н о й при о б р а б о т к е и с к л ю ч е н и я : свойство S y s t e m .
S o u r c e с о д е р ж и т название п р о г р а м м ы , в которой
произошло исключение; в свойстве
х р а н и т с я текст с о о б щ е н и я о б о ш и б к е ;
свойство
асе
содержит
точную
информацию
о т о м , в каком месте п р о г р а м м ы п р о и з о ш л о и с к л ю ч е н и е . Вот как в ы г л я д и т о т ф о р м а т и р о в а н н о е с о о б щ е н и е о б о ш и б к е , о т о б р а ж а е м о е н а ш е й п р о г р а м м о й на к о н с о л и после п о п ы т к и д е л е н и я на нуль: Ошибка
в
программе
DivideByZeroEx
[Attempted
at c#
to
divide
by
args)
in
bo 11
Как в и д и т е , эта и н ф о р м а ц и я п о з в о л я е т т о ч н о о п р е д е л и т ь тип и с к л ю ч е н и я , а также м е с т о , в к о т о р о м оно п р о и з о ш л о .
Использование нескольких блоков catch Теперь вы з н а е т е , ч т о , з а к л ю ч и в « н е н а д е ж н ы й » с точки зрения в о з н и к н о в е н и я о ш и б о к код в блок t r y , вы м о ж е т е п е р е х в а т и т ь и о б р а б о т а т ь и с к л ю ч е н и я в блоке c a t c h . З а м е т и м , что и с к л ю ч е н и я в о з н и к а ю т в т а к и х р а с п р о с т р а н е н н ы х с и т у а ц и я х , как вы¬ х о д за п р е д е л ы м а с с и в а или с т р о к и в п р о ц е с с е и н д е к с а ц и и , д е л е н и е на н у л ь , п о п ы т к а и с п о л ь з о в а н и я с с ы л к и с о з н а ч е н и е м n u l l , н е д о п у с т и м о е п р е о б р а з о в а н и е типа ссылки и т. д. С п о м о щ ь ю м е х а н и з м а и с к л ю ч е н и й все п о д о б н ы е о ш и б к и н е т р у д н о о б н а р у ж и т ь и о б р а б о т а т ь во в р е м я в ы п о л н е н и я п р и л о ж е н и я . Программа может предусмотреть общий
блок о б р а б о т к и
исключений различных
т и п о в , о п р е д е л и в один блок c a t c h , или создать н е с к о л ь к о таких блоков — по о д н о м у для и с к л ю ч е н и й к а ж д о г о типа. П р и м е р п р о г р а м м ы , в к о т о р о й р е а л и з о в а н такой с п о с о б о б р а б о т к и и с к л ю ч е н и й не¬ с к о л ь к и х т и п о в , п р и в е д е н в л и с т и н г е 9.3. Листинг
9.3.
Файл
using System; namespace MultiCatch class
MultiCatchApp
static
void
string
290
args)
errType; А
Г. В. Фролов. Язык
Самоучитель
do тип Завершить р а б о т у Деление на Выход з а границы Неправильное приведение Отрицательный р а з м е р И с п о л ь з о в а н и е ссылки errType try
int
i j
0;
- 5
case int
array
new
int[5]
case o b j e c t ch ch byte b
new
case i int
array
new 0;
case array [0]
Глава 9. Обработка исключений
291
ex) Попытка
деления
Выход
на
границы ex)
Неправильное
приведение
типов")
ex) Исключение
while
(errType
Эта п р о г р а м м а п о з в о л я е т в ы б р а т ь с к о н с о л и тип о ш и б к и , а затем в ы п о л н я е т про¬ граммный код, содержащий данную ошибку. При вводе с к о н с о л и числа 1 будет в ы п о л н е н о п и с а н н ы й ранее к о д , предприни¬ мающий попытку выполнить деление на нуль: case int i int j break;
0; 5
i
П о п ы т к а в ы п о л н е н и я этого кода во время р а б о т ы п р о г р а м м ы п р и в е д е т к возникно¬ вению
исключения
System. DivideByZeroException.
Он
обрабатывается
при
п о м о щ и с л е д у ю щ е г о блока c a t c h : ex) Попытка
деления
на
К а к в и д и т е , в н а ш е м случае вся о б р а б о т к а с в о д и т с я к в ы в о д у на к о н с о л ь соответ¬ ствующего сообщения об ошибке. Если ввести на к о н с о л и число 2, а затем н а ж а т ь к л а в и ш у Enter, будет в ы п о л н е н с л е д у ю щ и й код: 2 int 292
array
new В
Г. В. Фролов. Язык
Самоучитель
0
Здесь мы создаем м а с с и в ц е л ы х ч и с е л , с о д е р ж а щ и х 5 ячеек, а затем п ы т а е м с я запи¬ сать з н а ч е н и е в ш е с т у ю я ч е й к у . К о м п и л я т о р т а к у ю о ш и б к у о б н а р у ж и т ь не в состоя¬ нии, о д н а к о при
этого кода в о з н и к а е т и с к л ю ч е н и е
Для о б р а б о т к и этого и с к л ю ч е н и я м ы п р е д у с м о т р е л и такой блок c a t c h : ex) Выход
Исключение типа
за
границы
v
п (неправильное приведение
т и п о в ) создается с л е д у ю щ и м ф р а г м е н т о м п р о г р а м м ы :
object
ch
new
byte b
Здесь м ы создаем п е р е м е н н у ю c h типа o b j e c t ( н а п о м н и м , что все классы в С # н а с л е д у ю т с я от класса o b j e c t ) . н а о б ъ е к т класса
Char
Далее мы з а п и с ы в а е м
в
эту
(предназначенный для представления
переменную ссылку
символов U N I C O D E ) ,
чего и н и ц и а л и з и р у е м п е р е м е н н у ю c h с и м в о л о м з в е з д о ч к и . Финальное преобразовать не в о з р а ж а е т
действие, ссылку, против
вызывающее хранящуюся
такого
возникновение
в
переменной
преобразования,
исключения,
ch,
так как
в
тип
это
byte.
переменная
попытка
Компилятор
ch
имеет
тип
а п р е о б р а з о в а н и я ссылки базового класса в ссылку на д о ч е р н и й к л а с с разре¬ ш е н ы . Т е п е р ь в роли д о ч е р н е г о класса у нас в ы с т у п а е т класс b y t e . О д н а к о во время р а б о т ы п р о г р а м м ы в ы я с н я е т с я , что в ы п о л н и т ь н е о б х о д и м о е пре¬ образование
типов
невозможно,
в
результате
чего
возникает
исключение
Sys
t e m . I n v a l i d C a s t E x c e p t i o n . В о т его о б р а б о т ч и к : ex) Неправильное В не для
нашей
программе
всех
исключений,
ByZeroException,
мы
намеренно
а только
выход
за
для
приведение
предусмотрели
индивидуальную
трех
на нуль
границы
(деление
массива
обработку
System. IndexOutOfRange-
E x c e p t i o n и неправильное приведение типов S y s t e m . I n v a l i d C a s t E x c e p t i o n ) . Все о с т а л ь н ы е и с к л ю ч е н и я о б р а б а т ы в а ю т с я о д н и м у н и в е р с а л ь н ы м с п о с о б о м : Глава 9. Обработка исключений
293
ex) Исключение
ex
ToString
Вот что мы у в и д и м на э к р а н е при п о п ы т к е и с п о л ь з о в а н и я с с ы л к и , с о д е р ж а щ е й значение n u l l : Выберите тип ошибки: Завершить р а б о т у программы Д е л е н и е н а нуль Выход з а г р а н и ц ы м а с с и в а 3. Неправильное приведение типов Отрицательный размер массива 5 . И с п о л ь з о в а н и е ссылки n u l l 5 Исключение set to an i n s t a n c e of an o b j e c t . at Для
Object
reference
args) in 59
not c#
т е к с т а с о о б щ е н и я с и н ф о р м а ц и е й об и с к л ю ч е н и и мы о б р а т и л и с ь
к м е т о д у T o S t r i n g . Н а п о м н и м , что этот м е т о д о п р е д е л е н в я з ы к е С# для всех объек¬ тов и п о з в о л я е т п о л у ч и т ь т е к с т о в о е п р е д с т а в л е н и е объекта. Если же вам н у ж н а д е т а л и з и р о в а н н а я и н ф о р м а ц и я об и с к л ю ч е н и и , н е о б х о д и м о об¬ ращаться tion.
к
свойствам
класса
таким,
как
S o u r c e (название программы),
(текст сооб
щения об ошибке) и
S t a c k T r a c e ( и н ф о р м а ц и я о месте воз¬
никновения ошибки).
Исключение при арифметическом переполнении Так как для х р а н е н и я ц е л ы х чисел и с п о л ь з у е т с я ф и к с и р о в а н н о е к о л и ч е с т в о д в о и ч н ы х р а з р я д о в , з а в и с я щ е е от т и п а числа, то при в ы п о л н е н и и над э т и м и ч и с л а м и арифмети¬ ческих о п е р а ц и й в о з м о ж н о п е р е п о л н е н и е . Если не п р е д у с м о т р е т ь с п е ц и а л ь н ы х м е р , в р е з у л ь т а т е п е р е п о л н е н и я р е з у л ь т а т вы¬ ч и с л е н и й будет и с к а ж е н . О д н а к о м е х а н и з м и с к л ю ч е н и й п о з в о л я е т и з б а в и т ь програм¬ м ы о т о ш и б о к п о д о б н о г о рода. Р а с с м о т р и м п р о г р а м м у , и с х о д н ы й т е к с т к о т о р о й п р и в е д е н в л и с т и н г е 9.4. Листинг
9.4.
Файл
using System; namespace Overflow class static int 294
void x у
args) 200000; 100000; А
Фролов, Г.
Язык
Самоучитель
z
x *
у; 100000
int
z);
b 100000
int int
b)
z2;
unchecked zl
x
y; zl)
checked
z2
x
y; z2)
В н а ч а л ь н о м ф р а г м е н т е этой п р о г р а м м ы мы у м н о ж а е м ч и с л о 200 000 на 100 0 0 0 , в р е з у л ь т а т е чего по идее д о л ж н о п о л у ч и т ь с я з н а ч е н и е 20 000 000 000: int х int у int z
200000; 100000; х у; 100000
z)
О д н а к о , з а п у с т и в эту п р о г р а м м у н а в ы п о л н е н и е , н е т р у д н о у б е д и т ь с я , что р е з у л ь т а т будет равен о т р и ц а т е л ь н о м у числу -1 4 7 4 836 4 8 0 . В этом нет ничего у д и в и т е л ь н о г о : р а з р я д н о й сетки д а н н ы х типа i n t н е д о с т а т о ч н о для п р а в и л ь н о г о п р е д с т а в л е н и я ч и с л а 20 000 000 000. Глава 9. Обработка исключений
295
К о в а р с т в о этой о ш и б к и в т о м , что она не о б н а р у ж и в а е т себя ни на стадии компи¬ ляции п р о г р а м м ы , ни на стадии в ы п о л н е н и я . П р о г р а м м а п о л у ч а е т н е п р а в и л ь н ы й рсзультат, а п р о г р а м м и с т м о ж е т об этом д а ж е не д о г а д ы в а т ь с я . О д н а к о , и с п о л ь з у я о п е р а т о р c h e c k e d , м ы м о ж е м о б н а р у ж и т ь о ш и б к у н а стадии выполнения программы: try int
b 100000 ex)
В данном на
случае о п е р а т о р
переполнение.
При
c h e c k e d проверяет результат выполняемой операции
возникновении
переполнения
оператор
создает исключение
S y s t e m . O v e r f l o w E x c e p t i o n , к о т о р о е м о ж е т быть о б р а б о т а н о п р о г р а м м о й . Оператор u n c h e c k e d , напротив, позволяет отключить проверку на переполнение. Ключевые
слова
checked
и
unchecked
можно
использовать
для
включения
и выключения проверки переполнения и по-другому: int int
z2;
unchecked x
у;
x
y;
checked z2
ex)
Здесь мы п о к а з а л и , как п р о г р а м м а м о ж е т в к л ю ч и т ь или о т к л ю ч и т ь п р о в е р к у пере¬ п о л н е н и я для ц е л ы х ф р а г м е н т о в кода. Если при в ы п о л н е н и и ф р а г м е н т а кода н у ж н о создавать
исключение
Sys
Ex
p t i o n при
обнаружении
переполне-
такой ф р а г м е н т кода с л е д у е т з а к л ю ч и т ь в блок c h e c k e d . А н а л о г и ч н о блок u n c h e c k e d предназначен для отключения проверки переполнения. 296
А В
Г. В Фролов. Язык
Самоучитель
Стандартные классы исключений Класс E x c e p t i o n о б ы ч н о п р и м е н я е т с я в к а ч е с т в е у н и в е р с а л ь н о г о средства, позво¬ л я ю щ е г о о б р а б а т ы в а т ь о ш и б к и л ю б о г о типа. Для более т о н к о й к л а с с и ф и к а ц и и оши¬ бок л у ч ш е и с п о л ь з о в а т ь с т а н д а р т н ы е к л а с с ы , п о р о ж д е н н ы е о т класса E x c e p t i o n или р а з р а б о т а н н ы е вами с а м о с т о я т е л ь н о . С т а н д а р т н ы е классы о б р а б о т к и о ш и б о к п р е д у с м о т р е н ы п р а к т и ч е с к и для к а ж д о й б и б л и о т е к и классов Microsoft В о п и с а н и и м е т о д о в у к а з а н о , к а к и е ис¬ к л ю ч е н и я м о г у т создаваться при их в ы з о в е в случае в о з н и к н о в е н и я тех или и н ы х ошибочных ситуаций. Теперь в ы знаете, что д л я о д н о г о блока t r y м о ж н о о п р е д е л и т ь н е с к о л ь к о б л о к о в c a t c h , к о т о р ы е будут о б р а б а т ы в а т ь с я п о с л е д о в а т е л ь н о . Если в о з н и к н е т и с к л ю ч е н и е , то будет в ы п о л н е н тот блок c a t c h , в п а р а м е т р е к о т о р о г о это и с к л ю ч е н и е о б ъ я в л е н о . В том с л у ч а е , когда ни один блок не п о д х о д и т , в ы п о л н я е т с я блок с о б ъ я в л е н и е м клас¬ са E x c e p t i o n . А если такой блок не п р е д у с м о т р е н , и с к л ю ч е н и е б у д е т о б р а б о т а н о на у р о в н е с и с т е м ы и с п о л н е н и я В т а б л . 9.1 мы п е р е ч и с л и л и с т а н д а р т н ы е к л а с с ы и с к л ю ч е н и й С#. Таблица
Стандартные
классы
исключений
С# Описание
Класс
Б а з о в ы й класс д л я о б р а б о т к и и с к л ю ч е н и й , возни¬ Exception
к а ю щ и х при в ы п о л н е н и и а р и ф м е т и ч е с к и х опера¬ ций. Это такие и с к л ю ч е н и я , как и П р о и с х о д и т при п о п ы т к е з а п и с а т ь в
мас¬
сива д а н н ы е н е п р а в и л ь н о г о т и п а , о т л и ч н о г о от ти¬ па м а с с и в а Sys
Попытка деления целочисленного значения на нуль В о з н и к а е т при п о п ы т к е и с п о л ь з о в а т ь отрицатель¬
RangeException
ный и н д е к с в м а с с и в е или и н д е к с , в ы х о д я щ и й за г р а н и ц ы м а с с и в а Ошибка в процессе явного преобразования ссылки
Exception
No
Exception Глава 9. Обработка исключений
на о б ъ е к т б а з о в о г о класса в ссылку на о б ъ е к т про¬ и з в о д н о г о класса О ш и б к а при п о п ы т к е к о м б и н и р о в а н и я д в у х деле¬ гатов, не равных из-за т о г о , что тип в о з в р а щ а е м о г о з н а ч е н и я д е л е г а т о в отличен от v o i d П о п ы т к а и с п о л ь з о в а н и я с с ы л к и , с о д е р ж и м о е кото¬ рой р а в н о n u l l
297
Класс В о з н и к а е т , когда для в ы п о л н е н и я о п е р а ц и и соз¬ д а н и я о б ъ е к т а при п о м о щ и к л ю ч е в о г о слова new не х в а т а е т о п е р а т и в н о й п а м я т и
Exception
П е р е п о л н е н и е при в ы п о л н е н и и а р и ф м е т и ч е с к о й Exception
операции
Exception
П е р е п о л н е н и е стека. М о ж е т в о з н и к н у т ь при глу¬ боких р е к у р с и в н ы х в ы з о в а х м е т о д о в В о з н и к а е т в том с л у ч а е , когда с т а т и ч е с к и й конст¬ р у к т о р создает и с к л ю ч е н и е , а в п р о г р а м м е не пре¬ д у с м о т р е н а его о б р а б о т к а при п о м о щ и блока catch
Создание исключений М е х а н и з м о б р а б о т к и о ш и б о к С# был бы н е п о л н ы м , если бы п р о г р а м м а м предоставля¬ лась в о з м о ж н о с т ь ю о б р а б а т ы в а т ь и с к л ю ч е н и я , но не с о з д а в а т ь их. К а к и с л е д о в а л о о ж и д а т ь , п р о г р а м м ы С # м о г у т с о з д а в а т ь ( и н ы м и с л о в а м и , в о з б у ж д а т ь или п е р е д а в а т ь ) и с к л ю ч е н и я при п о м о щ и к л ю ч е в о г о слова t h r o w . Рассмотрим различные способы создания исключений.
Создание исключений класса Exception П р е д с т а в и м себе с л е д у ю щ у ю с и т у а ц и ю , и обрабатывать исключение.
в
которой
нам
бы
пригодилось создавать
П у с т ь мы о п р е д е л и л и в своей п р о г р а м м е класс P o i n t , п р е д н а з н а ч е н н ы й для хра¬ нения т о ч е к с п о л о ж и т е л ь н ы м и к о о р д и н а т а м и : class
Point
public public
uint uint
xPos; yPos;
public
Point
(int
x,
int
y)
xPos yPos
П р и с о з д а н и и н о в о г о о б ъ е к т а класса P o i n t м ы п е р е д а е м к о о р д и н а т ы т о ч к и конст¬ руктору: pt2
298
new P o i n t ( 0 , new
А В. Фролов, Г. В. Фролов. Язык
Самоучитель
П р о б л е м а з а к л ю ч а е т с я в т о м , что в этой р е а л и з а ц и и класса P o i n t к о н с т р у к т о р знак п е р е д а в а е м ы х ему к о о р д и н а т , п о э т о м у п р о г р а м м а по о ш и б к е м о ж е т создать точку с о т р и ц а т е л ь н ы м и к о о р д и н а т а м и . Конечно, конструктор может проверить знак своих параметров, но как сообщить вызы вающей программе об ошибке? Ведь конструктор не может возвращать никаких значений! Выход в создании и с к л ю ч е н и я при о б н а р у ж е н и и о т р и ц а т е л ь н ы х к о о р д и н а т ( л и с т и н г 9.5). Листинг
9.5.
Файл
using System; namespace PositivePoint class
Point
public public
uint uint
xPos;
public
Point
(int
0
у
i f (x
x,
int
y)
0)
xPos yPos else t h r o w new E x c e p t i o n "Обнаружена о т р и ц а т е л ь н а я
class
координата
PositivePointApp
static
void
Point Point
ptl pt2
args)
pt2;
new P o i n t ( 0 , new P o i n t ( - l ,
2) ex)
Console
9. Обработка исключений
Исключение
299
Вот новый вариант конструктора: public
Point
i f (х
(int
0
х,
у
xPos yPos
int
у)
0)
(uint)y;
else t h r o w new "Обнаружена
отрицательная
координата
при о б н а р у ж е н и и о ш и б к и к о н с т р у к т о р объекта класса P o i n t в о з б у ж д а е т с о с т о я н и е и с к л ю ч е н и я , п р е р ы в а я н о р м а л ь н ы й ход в ы п о л н е н и я п р о г р а м м ы . При этом в качестве класса
п а р а м е т р а к л ю ч е в о м у слову
throw
п е р е д а е т с я с с ы л к а на новый о б ъ е к т
Exception.
Обратите в н и м а н и е , что при создании и с к л ю ч е н и я мы передаем конструктору объекта класса E x c e p t i o n параметр
строку описания о ш и б о ч н о й ситуации.
Вот как в ы з ы в а ю щ и й м е т о д о б р а б а т ы в а е т н а ш е и с к л ю ч е н и е : Point Point
р pt2
ptl; pt2;
new new ex) Исключение
К а к в и д и т е , т е х н и к а о б р а б о т к и с о з д а н н о г о нами и с к л ю ч е н и я класса E x c e p t i o n н и ч е м н е о т л и ч а е т с я о т т е х н и к и о б р а б о т к и о п и с а н н ы х в ы ш е и с к л ю ч е н и й , создавае¬ мых с и с т е м о й и с п о л н е н и я Framework.
Новый класс на базе класса Exception О п и с а н н а я в ы ш е м е т о д и к а с о з д а н и я и с к л ю ч е н и я класса E x c e p t i o n н е позволяет пе р е д а т ь в в ы з ы в а ю щ у ю п р о г р а м м у н и к а к о й д о п о л н и т е л ь н о й и н ф о р м а ц и и , кроме тек¬ стового сообщения об ошибке. Ч т о б ы п р о г р а м м а могла в ы п о л н и т ь более п о л н у ю д и а г н о с т и к у п р и ч и н возникно¬ вения и с к л ю ч е н и я ,
создайте
на
базе
класса
E x c e p t i o n собственный
производный
класс. П о л я т а к о г о класса м о г у т и с п о л ь з о в а т ь с я для х р а н е н и я р а с ш и р е н н о й информа¬ ции об о ш и б к е . При этом д о п о л н и т е л ь н а я и н ф о р м а ц и я м о ж е т быть п е р е д а н а , напри¬ м е р , ч е р е з п а р а м е т р ы к о н с т р у к т о р а класса, п р о и з в о д н о г о о т класса E x c e p t i o n . Создание
собственного
класса для
обработки
исключений
позволяет
выполнить
более т о н к у ю о б р а б о т к у о ш и б о к п о с р а в н е н и ю той, что п о з в о л я е т класс E x c e p t i o n
300
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Р а с с м о т р и м п р о г р а м м у , и с х о д н ы й текст которой п р е д с т а в л е н в л и с т и н г е 9.6. Листинг
9.6.
Файл
using System; namespace PositivePointEx class
Exception
public public
int int
xErr; yErr;
public
x,
xErr yErr
class
int
y,
string
X;
Point
public public
uint uint
public
Point
i f (x
xPos;
(int
0
int
у
y)
0)
xPos yPos else t h r o w new "Обнаружена
class
отрицательная
y, координата
PositivePointExApp
static
void
Point Point
args)
ptl; pt2;
try ptl pt2
new new
Point(-l,
Глава 9. Обработка исключений
301
ex)
Исключение:
Здесь н а базе класса E x c e p t i o n м ы создали п р о и з в о д н ы й класс P o i n t E x c e p tion class
PointException
public int public int public
Exception
xErr; Err
PointException
xErr yErr
x,
int
y,
string
errMessage)
x; y;
О б р а т и т е в н и м а н и е , что
к о н с т р у к т о р класса P o i n t E x c e p t i o n в ы з ы в а е т конст
р у к т о р б а з о в о г о класса E x c e p t i o n .
Это н е о б х о д и м о для п р а в и л ь н о г о ф у н к ц и о н и р о
вания с и с т е м ы о б р а б о т к и и с к л ю ч е н и й . Расширенная
информация
об
ошибке,
а именно
ошибочные координаты точки,
х р а н я т с я в полях x E r r и y E r r класса P o i n t E x c e p t i o n .
Они и н и ц и а л и з и р у ю т с я
конструктором. П р и о б н а р у ж е н и и о ш и б к и в н о в о м в а р и а н т е к о н с т р у к т о р а класса P o i n t м ы соз даем
исключение
public
Point
i f (x xPos yPos
0
класса
(int
x,
int
и с п о л ь з у я для
этого
ключевое
слово
у)
0)
у
(uint)y;
else t h r o w new "Обнаружена
302
PointException,
отрицательная
y, координата А
Фролов, Г.
Фролов. Язык
Самоучитель
Помимо
текста
сообщения
об
ошибке
мы
передаем
конструктору
класса
P o i n t E x c e p t i o n дополнительную и н ф о р м а ц и ю — ошибочные координаты точки. О б р а б о т ч и к нашего
исключения об
ошибке
извлекает и з и
ошибочные
полей
объекта
координаты,
класса
Point-
о т о б р а ж а я все
это
на к о н с о л и :
ptl pt2
new P o i n t ( 0 , new P o i n t
0) 2 ex)
Таким о б р а з о м , теперь н а ш а п р о г р а м м а в состоянии не т о л ь к о о б н а р у ж и т ь о ш и б к у , но и о п р е д е л и т ь т о ч н ы е п р и ч и н ы ее в о з н и к н о в е н и я , с о о б щ и в их п о л ь з о в а т е л ю .
Конструкторы класса Exception Как мы уже говорили, и с к л ю ч е н и й С#.
В
классе
класс E x c e p t i o n является
б а з о в ы м д л я всех
E x c e p t i o n предусмотрено
несколько
классов
перегруженных
конструкторов: public public public public
Exception
П е р в ы й и з этих к о н с т р у к т о р о в самый п р о с т о й . О н п р е д н а з н а ч е н д л я т о г о , ч т о б ы в о з б у ж д а т ь с о с т о я н и е и с к л ю ч е н и я без т е к с т о в о г о с о о б щ е н и я о б о ш и б к е . В т о р о й к о н с т р у к т о р вам у ж е з н а к о м — его е д и н с т в е н н ы й п а р а м е т р п р е д н а з н а ч е н для передачи текстового сообщения в обработчик исключения. С п о м о щ ь ю т р е т ь е г о к о н с т р у к т о р а м о ж н о о б р а б а т ы в а т ь так н а з ы в а е м ы е внутрен ние (inner) и с к л ю ч е н и я . Э т о т к о н с т р у к т о р п о з в о л я е т и з м е н и т ь и с к л ю ч е н и е , а затем пе¬ р е д а т ь его для д а л ь н е й ш е й о б р а б о т к и . И н а к о н е ц , ч е т в е р т ы й к о н с т р у к т о р п о з в о л я е т и н и ц и а л и з и р о в а т ь о б ъ е к т класса E x c e p t i o n п о с л е д о в а т е л ь н ы м и д а н н ы м и (такие д а н н ы е будут р а с с м о т р е н ы п о з ж е ) . В п р е д ы д у щ е м п р и м е р е п р о г р а м м ы м ы , создавая п р о и з в о д н ы й класс P o i n t E x c e p t i o n , п е р е о п р е д е л и л и в нем т о л ь к о второй в а р и а н т к о н с т р у к т о р а . О д н а к о кор¬ р е к т н е е было бы п р е д у с м о т р е т ь п е р е г р у з к у всех ч е т ы р е х в а р и а н т о в , н а п р и м е р так: class
PointException
public public
int
Exception
xErr;
9. Обработка исключений
303
public
x,
int
y)
x,
int
y,
string
errMessage)
public PointException x, int Exception innerException) Exception
y,
string
errMessage,
xErr yErr
public
xErr yErr
xErr yErr
x;
PointException
x;
y;
public si,
xErr yErr
x, int y, StreamingContext s i , sc)
sc)
x; y;
П р и с о з д а н и и с о б с т в е н н ы х классов и с к л ю ч е н и й п р и н я т о д о б а в л я т ь к имени класса с у ф ф и к с « E x c e p t i o n » : так будет сразу п о н я т н о н а з н а ч е н и е класса. И м е н н о поэтому мы и назвали свой класс P o i n t E x c e p t i o n , а не к а к - н и б у д ь еще. Х о т я , р а з у м е е т с я , это п р а в и л о н е с т р о г о е
вы м о ж е т е в ы б р а т ь для н а з в а н и я т а к о г о класса л ю б о е имя.
Передача исключения для повторной обработки Как мы уже г о в о р и л и , ф у н к ц и я или м е т о д не всегда м о ж е т р е ш и т ь , что же д е л а т ь при в о з н и к н о в е н и и о ш и б к и . Н е к о т о р ы е в и д ы о ш и б о к могут быть о б р а б о т а н ы в том же с а м о м м е т о д е , где они п р о и з о ш л и , а н е к о т о р ы е — т о л ь к о в о д н о м из в ы з ы в а ю щ и х ме¬ т о д о в , р а с п о л о ж е н н ы х вверх п о и е р а р х и и в ы з о в о в . Н а с а м о м в е р х н е м у р о в н е н а х о д и т с я о б р а б о т ч и к и с к л ю ч е н и й среды и с п о л н е н и я F r a m e w o r k , к о т о р о м у п е р е д а ю т с я все и с к л ю ч е н и я , не о б р а б о т а н н ы е в п р о г р а м м е . Как п р а в и л о , этот о б р а б о т ч и к п р о с т о з а в е р ш а е т работу п р о г р а м м ы с в ы в о д о м на экран сообщения об ошибке.
304
А В. Фролов, Г. В. Фролов.
Самоучитель
В л и с т и н г е 9.7 мы привели и с х о д н ы й т е к с т п р о г р а м м ы , в которой о б р а б о т ч и к ис¬ к л ю ч е н и я в ы п о л н я е т некие о б р а б а т ы в а ю щ и е д е й с т в и я , а затем п е р е д а е т и с к л ю ч е н и е вверх по и е р а р х и и . 9.7.
Файл
using System; namespace class
Point
public public public
uint uint int
xPos; yPos;
public
Point
(int
x,
int
y)
try ratio
x
catch
class
PointExInternalApp
static
void
Point Point Point
args)
ptl; pt2;
try ptl pt2 pt3
new new new
Point(-l,
ex)
Исключение:
Глава
Обработка
305
В
центре
нашей
программы
находится
класс
Point,
представляющий
точку
н а п л о с к о с т и к о о р д и н а т . Д о п о л н и т е л ь н о к полям x P o s и y P o s , х р а н я щ и м к о о р д и н а т ы т о ч к и , мы о б ъ я в и л и в этом классе поле r a t i o , п р е д н а з н а ч е н н о е для х р а н е н и я отно¬ шений к о о р д и н а т : class
Point
public public public
uint xPos; uint yPos; int r a t i o ;
К о н с т р у к т о р класса P o i n t при п о м о щ и о п е р а ц и и д е л е н и я , ключения: public
Point
(int
х,
int
вычисляет
о т н о ш е н и е к о о р д и н а т п о осям Х и Y к о т о р о й м о ж е т п р и в е с т и к п о я в л е н и ю ис¬
у)
try ratio
x
catch
О б р а т и т е в н и м а н и е на о б р а б о т ч и к и с к л ю ч е н и й , п р е д у с м о т р е н н ы й в к о н с т р у к т о р е класса P o i n t . Этот обработчик вначале выводит на консоль сообщение об о ш и б к е (это имитация об¬ работки о ш и б к и внутри того метода, где возникло исключение), а затем передает исклю чение в в ы з ы в а ю щ и й метод при п о м о щ и ключевого слова t h r o w без параметров. В н у т р и в ы з ы в а ю щ е г о метода м ы т о ж е п р е д у с м о т р е л и о б р а б о т ч и к и с к л ю ч е н и й :
pt2 pt3
new new new
0) (-1,
ex) Исключение: Если з а п у с т и т ь нашу п р о г р а м м у на в ы п о л н е н и е , то на к о н с о л и появятся следую¬ щие с о о б щ е н и я :
306
А
Фролов, Г. В. Фролов. Язык С# Самоучитель
Произошло и с к л ю ч е н и е Исключение: A t t e m p t e d
to
divide
by
zero.
П е р в о е с о о б щ е н и е в ы в е д е т о б р а б о т ч и к и с к л ю ч е н и й , п р е д у с м о т р е н н ы й в конструк¬ торе класса P o i n t , а второе — м е т о д M a i n , с о з д а ю щ и й о б ъ е к т этого класса (и н е я в н о вызывающий упомянутый конструктор).
Применение блока finally В н е к о т о р ы х с л у ч а я х при о б р а б о т к е и с к л ю ч е н и й с п р и м е н е н и е м блоков t r y и c a t c h имеет с м ы с л д о п о л н и т е л ь н о п р е д у с м о т р е т ь блок Э т о т блок в ы п о л н я е т с я всегда, вне з а в и с и м о с т и от т о г о , п р о и з о ш л о ли и с к л ю ч е н и е в процессе р а б о т ы блока t r y или нет. Н а и л у ч ш е е п р и м е н е н и е д л я блока освобождение ресурсов, заказан ных п р о г р а м м о й перед в о з н и к н о в е н и е м и с к л ю ч е н и й . Х о т я система с б о р к и м у с о р а ав¬ т о м а т и ч е с к и о с в о б о ж д а е т н е н у ж н у ю более о п е р а т и в н у ю п а м я т ь , д р у г и е р е с у р с ы , та¬ кие, как, н а п р и м е р , о т к р ы т ы е п о т о к и , с в я з а н н ы е с ф а й л а м и , следует з а к р ы в а т ь я в н ы м образом, вызывая соответствующие методы. В л и с т и н г е 9.8 мы п р и в е л и п р и м е р п р о г р а м м ы , д е м о н с т р и р у ю щ е й и с п о л ь з о в а н и е блока finally. Листинг using
9.8.
Файл
ch09\Finally\FinallyApp.cs
System; Finally
class
FinallyApp
static С
void
static
void
static
void
path)
data) в
int x int у static
5
файл
строки
x;
void
Глава 9. Обработка исключений
args)
307
ex) Исключение:
Так как мы е щ е не р а с с к а з ы в а л и вам о р а б о т е с ф а й л а м и , в м е с т о в ы з о в а р е а л ь н ы х м е т о д о в , п р е д н а з н а ч е н н ы х для записи в ф а й л , мы будем п о л ь з о в а т ь с я и м и т а т о р а м и . П е р е д тем как п р и с т у п и т ь к р а б о т е с ф а й л о м , его н е о б х о д и м о о т к р ы т ь . Эта опера¬ ция и м и т и р у е т с я в н а ш е й п р о г р а м м е openFile: static
void
path)
В качестве п а р а м е т р а методу п е р е д а е т с я путь к о т к р ы в а е м о м у файлу, од¬ н а к о , в н а ш е м и м и т а т о р е никак не и с п о л ь з у е т с я . П о с л е того как файл о т к р ы т , в него м о ж н о з а п и с ы в а т ь д а н н ы е . Эта о п е р а ц и я ими¬ т и р у е т с я в нашей п р о г р а м м е м е т о д о м w r i t e F i l e : static
void
data) в
int x int у
0; 5
файл
строки
x;
В м е с т о з а п и с и с т р о к и , передаваемой методу в качестве е д и н с т в е н н о г о п а р а м е т р а , этот м е т о д о т о б р а ж а е т строку на к о н с о л и . А затем о н . . . в ы п о л н я е т д е л е н и е числа 5 на 0, ч т о б ы вызвать исключение. Т а к и м с п о с о б о м мы и м и т и р у е м в о з н и к н о в е н и е о ш и б к и в п р о ц е с с е записи д а н н ы х в файл. И н а к о н е ц , п о с л е р а б о т ы необходимо з а к р ы т ь файл. Если этого не с д е л а т ь , с о д е р ж и м о е файла на д и с к е будет и с к а ж е н о . Для и м и т а ц и и з а к р ы т и я файла в нашей про грамме применяется метод c l o s e F i l e : static
308
void
closeFile()
А В
Г, В. Фролов
Самоучитель
Теперь з а й м е м с я самой п р о г р а м м о й . М е т о д M a i n нашей
программы
о т к р ы в а е т файл
посредством метода
а затем п ы т а е т с я з а п и с а т ь в него т е к с т о в у ю строку м е т о д о м
ex)
О д н а к о вызов м е т о д а w r i t e F i l e п р и в е д е т к в о з н и к н о в е н и ю о ш и б к и д е л е н и я н а н у л ь , в р е з у л ь т а т е чего у п р а в л е н и е будет п е р е д а н о блоку c a t c h , о б р а б а т ы в а ю щ е ¬ му о ш и б к у . П о с л е з а в е р ш е н и я о б р а б о т к и в дело в к л ю ч и т с я блок i n a l l y . O H будет в ы п о л н е н в л ю б о м с л у ч а е , д а ж е при в о з н и к н о в е н и и и с к л ю ч е н и я в п р о ц е с с е записи д а н н ы х в файл. В этом н е т р у д н о у б е д и т ь с я , з а п у с т и в п р о г р а м м у и п о с м о т р е в на с о о б щ е н и я , к о т о р ы е она в ы в е д е т на к о н с о л ь : Открытие Запись в Закрытие
файла файл с т р о к и t e s t Attempted файла
to
divide
by
zero.
К а к в и д и т е , в н а ч а л е п р о и з о ш л о о т к р ы т и е файла, затем — п о п ы т к а записи д а н н ы х в файл. П о с л е этого п р о и з о ш л о и с к л ю ч е н и е , и наша п р о г р а м м а его о б р а б о т а л а . В к о н к о н ц о в файл
был з а к р ы т при п о м о щ и метода c l o s e F i l e ,
Глава 9. Обработка исключений
в ы з в а н н о г о в блоке
309
Глава 10. Многопоточность Как п р а в и л о , н о р м а л ь н ы й ч е л о в е к с п о с о б е н в ы п о л н я т ь н е с к о л ь к о задач Н е с м о т р я на то что врачи с ч и т а ю т это в р е д н ы м , м н о г и е л ю б я т читать во время еды или с м о т р е т ь т е л е в и з о р , у х и т р я я с ь при этом р е а г и р о в а т ь на р е п л и к и о к р у ж а ю щ и х . к о м п ь ю т е р ы также могут д е л а т ь о д н о в р е м е н н о н е с к о л ь к о дел. На¬ п р и м е р , пока п е ч а т а е т с я д о к у м е н т , м о ж н о п р и н я т ь почту или о т р е д а к т и р о в а т ь д р у г и е д о к у м е н т ы . Р а з у м е е т с я , ч т о б ы такое стало в о з м о ж н ы м , О С д о л ж н а д о п у с к а т ь парал¬ лельную работу нескольких программ. Если ОС на это с п о с о б н а , она н а з ы в а е т с я многопоточной ( m u l t i - t h r e a d e d ) . При этом работа
каждой
программы
осуществляется
в
рамках
одного
потока
исполнения,
а ц е н т р а л ь н ы й п р о ц е с с о р п о с т о я н н о п е р е к л ю ч а е т с я с о д н о г о п о т о к а на д р у г о й . Те О С , к о т о р ы е м о г у т в к а ж д ы й д а н н ы й м о м е н т в р е м е н и и с п о л н я т ь т о л ь к о одну п р о г р а м м у , называются
(single-threaded).
В мир п е р с о н а л ь н ы х к о м п ь ю т е р о в м н о г о п о т о ч н о с т ь
вторглась о т н о с и т е л ь н о
не¬
д а в н о , и д а л е к о не к а ж д ы й п р о г р а м м и с т у м е е т и с п о л ь з о в а т ь все ее п р е и м у щ е с т в а . Во м н о г и х с л у ч а я х м н о г о п о т о ч н о с т ь в ц е л о м б л а г о п р и я т н о с к а з ы в а е т с я на производи¬ т е л ь н о с т и с и с т е м ы , так как во в р е м я о ж и д а н и я о д н и х задач с в о ю работу могут выпол¬ нять д р у г и е з а д а ч и , г о т о в ы е для э т о г о . Н а п р и м е р , если вы р а б о т а е т е в И н т е р н е т е , то м о ж е т е о д н о в р е м е н н о п о д к л ю ч и т ь с я к н е с к о л ь к и м с е р в е р а м F T P и W e b , « п е р е к а ч и в а я » сразу н е с к о л ь к о ф а й л о в и з а г р у ж а я н е с к о л ь к о д о к у м е н т о в H T M L . П р и этом еще м о ж н о о т п р а в л я т ь или п о л у ч а т ь элек¬ т р о н н у ю почту. О б щ а я с к о р о с т ь п е р е д а ч и д а н н ы х в этом случае будет в ы ш е , чем при п о о ч е р е д н о й р а б о т е с с е р в е р а м и , — пока один из них н а х о д и т с я в с о с т о я н и и ожида¬ ния, вы будете п о л у ч а т ь д а н н ы е от д р у г о г о . Такое у в е л и ч е н и е средней скорости пере¬ дачи д а н н ы х в о з м о ж н о из-за т о г о , что ч е р е з о б щ и й канал И н т е р н е т а м о г у т одновре¬ м е н н о п р о х о д и т ь п а к е т ы д а н н ы х , п р е д н а з н а ч е н н ы е для р а з л и ч н ы х а д р е с а т о в . О д н а к о все это касается п о л ь з о в а т е л е й . А какие п р е и м у щ е с т в а п о л у ч а т от поточности
программисты?
П р о г р а м м и с т ы с м о г у т с о з д а в а т ь п р и л о ж е н и я , р а б о т а ю щ и е в м н о г о п о т о ч н о м ре¬ ж и м е . При этом о т д е л ь н ы е к о м п о н е н т ы т а к и х п р и л о ж е н и й с м о г у т р а б о т а т ь одновре¬ м е н н о , н е м е ш а я д р у г другу. О ч е н ь часто м н о г о п о т о ч н о с т ь и с п о л ь з у е т с я при в ы п о л н е н и и к а к и х - л и б о длитель¬ ных о п е р а ц и й . При этом к о д , о б е с п е ч и в а ю щ и й ф у н к ц и о н и р о в а н и е п о л ь з о в а т е л ь с к о г о и н т е р ф е й с а , р а б о т а е т в р а м к а х о д н о г о п о т о к а , а к о д , в ы п о л н я ю щ и й д л и т е л ь н у ю опе¬ рацию, — в рамках другого. В к а ч е с т в е п р и м е р а мы м о ж е м д а н н ы х C r a s h U n d o L a b o r a t o r y (рис.
привести п р о г р а м м н ы й к о м п л е к с в о с с т а н о в л е н и я 10.1), р а з р а б о т а н н ы й о д н и м и з авторов этой книги
для с л у ж б ы в о с с т а н о в л е н и я д а н н ы х
310
drive Drive S:\ opened
partition
Пример
многопоточного
приложения
В окне P a r t i t i o n S c a n n e r о т о б р а ж а е т с я х о д п р о ц е с с а с к а н и р о в а н и я д и с к а , выпол¬ н я е м о г о с ц е л ь ю поиска у ц е л е в ш и х р а з д е л о в . П р и этом для в ы п о л н е н и я д л и т е л ь н о г о п р о ц е с с а с к а н и р о в а н и я запускается д о п о л н и т е л ь н ы й п о т о к , р а б о т а ю щ и й в р а м к а х того же п р о ц е с с а , в р а м к а х к о т о р о г о р а б о т а е т и г л а в н ы й поток и с п о л н е н и я п р о г р а м м ы . Зачем с к а н и р о в а т ь диск в о т д е л ь н о м п о т о к е ? Т о л ь к о так м ы смогли д о б и т ь с я , ч т о б ы в о в р е м я д л и т е л ь н о г о с к а н и р о в а н и я м о ж н о было п р о д о л ж а т ь работу с п р о г р а м м о й C r a s h U n d o L a b o r a t o r y нирования,
просматривать
предварительные
результаты
следить за х о д о м ска¬
сканирования,
прерывать
п р о ц е с с и т. п. Если к о м п л е к с C r a s h U n d o Laboratory и с п о л ь з у е т с я для у д а л е н н о г о в о с с т а н о в л е н и я д а н н ы х , то он создает о т д е л ь н ы е потоки для п е р е д а ч и у п р а в л я ю щ и х к о м а н д и резуль¬ татов их и с п о л н е н и я через И н т е р н е т . Работа в И н т е р н е т е ( о с о б е н н о ч е р е з м о д е м )
связана с з а д е р ж к а м и и о б р ы в а м и свя¬
зи, п о э т о м у для ее в ы п о л н е н и я создается о т д е л ь н ы й поток. П р и этом н е с т а б и л ь н о с т ь п р о ц е с с а п е р е д а ч и д а н н ы х через И н т е р н е т не будет сказываться на общей работоспо¬ собности комплекса.
Глава 10.
Мы р е к о м е н д у е м п р и м е н я т ь м н о г о п о т о ч н о с т ь в с л е д у ю щ и х с л у ч а я х : •
для в ы п о л н е н и я д л и т е л ь н ы х п р о ц е д у р , х о д о м к о т о р ы х н у ж н о у п р а в л я т ь ;
•
для о т д е л е н и я п р о г р а м м н о г о кода, о т в е т с т в е н н о г о за ф у н к ц и о н и р о в а н и е пользова¬ т е л ь с к о г о и н т е р ф е й с а , о т кода, в ы п о л н я ю щ е г о к а к и е - л и б о д л и т е л ь н ы е о п е р а ц и и ; при к с е р в е р а м и с л у ж б а м И н т е р н е т а , базам д а н н ы х или при п е р е д а ч е д а н н ы х по сети;
•
в с л у ч а е , когда н у ж н о о д н о в р е м е н н о в ы п о л н я т ь н е с к о л ь к о з а д а ч , и м е ю щ и х р а з н ы й приоритет.
М н о г о п о т о ч н о с т ь с л е д у е т п р и м е н я т ь т о л ь к о в тех с л у ч а я х , когда она действитель¬ но нужна. И н а ч е ни к ч е м у , кроме у х у д ш е н и я п р о и з в о д и т е л ь н о с т и , это не приведет. К р о м е т о г о , м н о г о п о т о ч н ы е п р и л о ж е н и я н а м н о г о с л о ж н е е о т л а ж и в а т ь , чем т о ч н ы е . П р и ч и н а этого з а к л ю ч а е т с я в т о м , что п р и х о д и т с я с л е д и т ь за п р а в и л ь н о й син¬ х р о н и з а ц и е й п о т о к о в , р а б о т а ю щ и х о д н о в р е м е н н о и з а в и с я щ и х д р у г от друга.
З а в с ю и с т о р и ю с у щ е с т в о в а н и я О С для п е р с о н а л ь н ы х к о м п ь ю т е р о в было р а з р а б о т а н о н е с к о л ь к о м о д е л е й многопоточности. Э т о п е р е к л ю ч а т е л ь н а я , с о в м е с т н а я и в ы т е с
няющая Р а с с к а ж е м об э т и х м о д е л я х м н о г о п о т о ч н о с т и в п о р я д к е их в н е д р е н и я в ОС персо¬ нальных компьютеров.
Переключательная многопоточность В о в р е м е н а п е р в ы х п е р с о н а л ь н ы х к о м п ь ю т е р о в , когда п о в с е м е с т н о н а и б о л ь ш е й попу¬ л я р н о с т ь ю п о л ь з о в а л а с ь о д н о п о т о ч н а я О С M S - D O S , п о л ь з о в а т е л ю была д о с т у п н а так называемая переключательная основанная главным образом на п р и м е н е н и и так н а з ы в а е м ы х резидентных программ. Р е з и д е н т н ы е п р о г р а м м ы з а г р у ж а л и с ь в о п е р а т и в н у ю п а м я т ь к о м п ь ю т е р а , остава¬ ясь там до п е р е з а г р у з к и О С . Д о в о л ь н о п о п у л я р н ы е в свое в р е м я р е з и д е н т н ы е кальку¬ л я т о р ы п о з в о л я л и , н а п р и м е р , н е п р е р ы в а я р а б о т ы п р о г р а м м ы р е д а к т о р а текста или д р у г о й п р о г р а м м ы , в ы п о л н я т ь а р и ф м е т и ч е с к и е в ы ч и с л е н и я . Для п е р е к л ю ч е н и я от ре¬ д а к т и р о в а н и я т е к с т а к в ы ч и с л е н и я м на р е з и д е н т н о м к а л ь к у л я т о р е и о б р а т н о н у ж н о было н а ж а т ь на к л а в и а т у р е ту или и н у ю к о м б и н а ц и ю к л а в и ш . К м о м е н т у п о я в л е н и я н а с т о я щ и х м н о г о п о т о ч н ы х ОС I B M O S / 2 и Microsoft Win¬ d o w s б ы л о с о з д а н о в е л и к о е м н о ж е с т в о с а м ы х р а з н о о б р а з н ы х и часто н е с о в м е с т и м ы х м е ж д у собой р е з и д е н т н ы х п р о г р а м м для M S - D O S . С р е д и них были как п р о с т ы е , так и д о с т а т о ч н о с л о ж н ы е п р о г р а м м ы , н а п р и м е р п р о г р а м м а Borland SideKick, выполняю¬ щая ф у н к ц и и п е р с о н а л ь н о г о о р г а н а й з е р а .
Совместная П о я в л е н и е О С Microsoft W i n d o w s версии 3 . 0 , р а б о т а в ш е й как о б о л о ч к а для M S - D O S , с т и м у л и р о в а л о п о я в л е н и е п р и л о ж е н и й д л я Microsoft W i n d o w s , р а б о т а в ш и х в р е ж и м е т а к н а з ы в а е м о й совместной многопоточности ( c o o p e r a t i v e m u l t i - t h r e a d i n g ) .
312
Для
с о в м е с т н о й м н о г о п о т о ч н о с т и п р и л о ж е н и я Microsoft W i n d o w s соз¬
д а в а л и с ь о п р е д е л е н н ы м о б р а з о м и время от в р е м е н и передавали д р у г д р у г у управле¬ ние. В р е з у л ь т а т е создавалась и л л ю з и я о д н о в р е м е н н о й работы н е с к о л ь к и х приложе¬ ний. А н а л о г и ч н ы й п р и н ц и п п р и м е н я л с я в сетевой ОС Novell N e t W a r e , а т а к ж е в ОС компьютеров Macintosh компании Apple. Совместная
многопоточность
решила
проблемы
совместимости,
которые
были
слабым м е с т о м р е з и д е н т н ы х п р о г р а м м . Т е п е р ь п о л ь з о в а т е л ь мог з а п у с т и т ь сразу не¬ с к о л ь к о п р и л о ж е н и й и п е р е к л ю ч а т ь с я между ними при н е о б х о д и м о с т и . М н о г и е поль¬ зователи так и д е л а л и , о д н а к о в о з м о ж н о с т и м н о г о п о т о ч н о с т и при этом ф а к т и ч е с к и не з а д е й с т в о в а л и с ь , так как п р и л о ж е н и я р а б о т а л и по о ч е р е д и . Н е с м о т р я на то что ОС Microsoft W i n d o w s версии
п о з в о л я е т з а п у с т и т ь , напри¬
мер, ф о р м а т и р о в а н и е д и с к е т ы и на этом фоне р а б о т а т ь с д р у г и м и п р и л о ж е н и я м и , едва ли н а й д е т с я много ж е л а ю щ и х п о с т у п а т ь т а к и м о б р а з о м . Д е л о в том, ч т о , пока д и с к е т а не
будет о т ф о р м а т и р о в а н а ,
все о с т а л ь н ы е з а п у щ е н н ы е п р и л о ж е н и я будут р а б о т а т ь
очень м е д л е н н о . Е щ е один с у щ е с т в е н н ы й н е д о с т а т о к с о в м е с т н о й м н о г о п о т о ч н о с т и п р о я в л я е т с я при запуске н е д о с т а т о ч н о х о р о ш о о т л а ж е н н ы х п р и л о ж е н и й . Если п о к а к о й - л и б о п р и ч и н е приложение
не
приложениям,
сможет
периодически
работа всей
системы
передавать
управление
будет з а б л о к и р о в а н а и
другим
запущенным
пользователю
т о л ь к о н а ж а т ь к о м б и н а ц и ю из трех и з в е с т н ы х к л а в и ш
останется кнопку
а п п а р а т н о г о сброса, р а с п о л о ж е н н у ю н а к о р п у с е к о м п ь ю т е р а .
Вытесняющая многопоточность В
отличие
(preemptive
от
совместной
mult-threading)
многопоточности
предполагает выделение
вытесняющая всем
запущенным
многопоточность приложениям
квантов времени е использованием системного таймера. Не с л е д у е т д у м а т ь , что у с п е ц и а л и с т о в к о м п а н и и Microsoft не х в а т и л о у м а п р и м е нить в ы т е с н я ю щ у ю м н о г о п о т о ч н о с т ь в п е р в ы х в е р с и я х о б о л о ч к и Microsoft W i n d o w s . Она была и с п о л ь з о в а н а в ОС OS/2 версий 1.0
1.3, к о т о р а я в те в р е м е н а р а з р а б а т ы
валась с о в м е с т н о к о м п а н и я м и Microsoft и I B M . К с о ж а л е н и ю , слабая а р х и т е к т у р а п р о ц е с с о р а Intel 8 0 2 8 6 , н е д о с т а т о ч н а я произво¬ д и т е л ь н о с т ь в ы п у с к а в ш и х с я тогда к о м п ь ю т е р о в и м а л ы й о б ъ е м о п е р а т и в н о й п а м я т и , у с т а н о в л е н н о й в к о м п ь ю т е р а х п о д а в л я ю щ е г о числа п о л ь з о в а т е л е й (1 — 2 М б а й т ) , по¬ м е ш а л и ш и р о к о м у р а с п р о с т р а н е н и ю O S / 2 . Эта ОС с и с т и н н о й в ы т е с н я ю щ е й м у л ь т и з а д а ч н о с т ь ю р а б о т а л а очень м е д л е н н о и п р о и г р а л а с р а ж е н и е более л е г к о в е с н о й обо¬ л о ч к е Microsoft W i n d o w s версии 3.1. В е д ь в к о н е ч н о м счете пользователям было все равно,
какой
тип
многопоточности
применяется
в
ОС,
их и н т е р е с о в а л а
скорость
и удобство работы. С е г о д н я ситуация и з м е н и л а с ь . С о в р е м е н н ы е О С для п е р с о н а л ь н ы х к о м п ь ю т е р о в , т а к и е , как Microsoft W i n d o w s 9 5 / 9 8 / М Е , Microsoft W i n d o w s N T / 2 0 0 0 / X P , I B M W a r p ( в п р о ч е м , эту ОС у ж е не н а з о в е ш ь
а т а к ж е н а б и р а ю щ а я попу
л я р н о с т ь ОС L i n u x р а б о т а ю т в р е ж и м е и с т и н н о й в ы т е с н я ю щ е й м н о г о п о т о ч н о с т и . Глава 10 Многопоточность
Все п р и л о ж е н и я , в среде таких О С , г а р а н т и р о в а н н о п о л у ч а ю т для себя к в а н т ы в р е м е н и по п р е р ы в а н и ю от т а й м е р а . П р и этом н а к л а д н ы е р а с х о д ы на м н о г о п о т о ч н о с т ь к о м п е н с и р у ю т с я более р а з у м н ы м и с п о л ь з о в а н и е м р е с у р с о в и высокой про¬ изводительностью к о м п ь ю т е р о в , поэтому пользователь их не почувствует (конечно, если в к о м п ь ю т е р е у с т а н о в л е н а о п е р а т и в н а я память д о с т а т о ч н о г о о б ъ е м а ) . Пользователи многопоточных ОС получили возможность не просто переключаться с о д н о й задачи на д р у г у ю , а р е а л ь н о р а б о т а т ь о д н о в р е м е н н о с н е с к о л ь к и м и а к т и в н ы м и п р и л о ж е н и я м и . П р о г р а м м и с т ы же п о л у ч и л и в свои руки н о в ы й и н с т р у м е н т , с помо¬ щ ь ю к о т о р о г о они могут р е а л и з о в а т ь м н о г о п о т о ч н у ю о б р а б о т к у д а н н ы х , н е заостряя на этом в н и м а н и я п о л ь з о в а т е л я . Н а п р и м е р , в п р о ц е с с е р е д а к т и р о в а н и я д о к у м е н т а тек¬ с т о в ы й п р о ц е с с о р м о ж е т з а н и м а т ь с я н у м е р а ц и е й л и с т о в или п о д г о т о в к о й д о к у м е н т а для печати на п р и н т е р е . С о з д а в а я п р о г р а м м ы С# д л я О С Microsoft W i n d o w s вы можете реали¬ зовать в них все п р е и м у щ е с т в а м н о г о п о т о ч н о с т и , п р и ч е м , как вы скоро у в и д и т е , это м о ж н о сделать о т н о с и т е л ь н о л е г к о .
Процессы, потоки и приоритеты П р е ж д е чем п р и с т у п и т ь к о п и с а н и ю п р а к т и ч е с к и х п р и е м о в п р и м е н е н и я м н о г о п о т о ч ности в п р о г р а м м а х С # , с л е д у е т у т о ч н и т ь н е к о т о р ы е т е р м и н ы . О б ы ч н о в л ю б о й мно¬ г о п о т о ч н о й ОС в ы д е л я ю т т а к и е о б ъ е к т ы , как п р о ц е с с ы и п о т о к и . М е ж д у ними суще¬ ствует б о л ь ш а я р а з н и ц а , к о т о р у ю с л е д у е т четко себе п р е д с т а в л я т ь . В с и с т е м е испол¬ нения F r a m e w o r k д о б а в и л о с ь е щ е одно новое приложения, представленный классом
Процесс (process) ОС — это о б ъ е к т , к о т о р ы й создается О С , к о г д а п о л ь з о в а т е л ь запус¬ кает п р и л о ж е н и е . П р о ц е с с у в ы д е л я е т с я о т д е л ь н о е а д р е с н о е п р о с т р а н с т в о , п р и ч е м это п р о с т р а н с т в о ф и з и ч е с к и н е д о с т у п н о для д р у г и х п р о ц е с с о в . П р о ц е с с м о ж е т р а б о т а т ь с ф а й л а м и или с д р у г и м и п р о ц е с с а м и через к а н а л ы , соз¬ д а н н ы е ОС в о п е р а т и в н о й п а м я т и , в л о к а л ь н о й сети или ч е р е з И н т е р н е т . К о г д а в среде Microsoft W i n d o w s вы з а п у с к а е т е т е к с т о в ы й п р о ц е с с о р Microsoft W o r d for W i n d o w s или п р о г р а м м у к а л ь к у л я т о р а , вы тем с а м ы м создаете н о в ы й п р о ц е с с .
Поток Для к а ж д о г о п р о ц е с с а ОС с о з д а е т один г л а в н ы й поток (thread), к о т о р ы й является на¬ б о р о м в ы п о л н я ю щ и х с я п о о ч е р е д и к о м а н д ц е н т р а л ь н о г о п р о ц е с с о р а . П р и необходи¬ м о с т и г л а в н ы й п о т о к м о ж е т с о з д а в а т ь д р у г и е п о т о к и , п о л ь з у я с ь для этого программ¬ ным и н т е р ф е й с о м О С (Application P r o g r a m Interface, A P I ) . В с е п о т о к и , с о з д а н н ы е п р о ц е с с о м , в ы п о л н я ю т с я в а д р е с н о м п р о с т р а н с т в е этого п р о ц е с с а и и м е ю т д о с т у п к р е с у р с а м процесса. О д н а к о п о т о к о д н о г о п р о ц е с с а не име¬ ет н и к а к о г о д о с т у п а к р е с у р с а м задачи д р у г о г о процесса, так как они р а б о т а ю т в раз¬ н ы х а д р е с н ы х п р о с т р а н с т в а х . При н е о б х о д и м о с т и в з а и м о д е й с т в и я м е ж д у п р о ц е с с а м и или п о т о к а м и , п р и н а д л е ж а щ и м и р а з н ы м п р о ц е с с а м , с л е д у е т п о л ь з о в а т ь с я с п е ц и а л ь н о п р е д н а з н а ч е н н ы м и для этого с и с т е м н ы м и с р е д с т в а м и . А В
Г. В. Фролов. Язык С# Самоучитель
Домен приложения AppDomain В ы ш е мы п е р е ч и с л и л и п о н я т и я и п р и м е н я ю щ и е с я при с о з д а н и и многопо¬ т о ч н ы х п р и л о ж е н и й для ОС Microsoft W i n d o w s . Здесь и м е ю т с я в виду о б ы ч н ы е ис¬ п о л н я е м ы е п р и л о ж е н и я . Что же касается п р о г р а м м С # , то, как мы у ж е г о в о р и л и в на чале этой к н и г и , они в ы п о л н я ю т с я не сами по себе, а под у п р а в л е н и е м с и с т е м ы F r a m e w o r k . Эта же система отвечает и за р е а л и з а ц и ю м н о г о п о т о ч н о с т и в п р и л о ж е н и ях, н а п и с а н н ы х с и с п о л ь з о в а н и е м я з ы к а С#. При этом F r a m e w o r k м о ж е т с о з д а в а т ь на базе о д н о г о с и с т е м н о г о п р о ц е с с а не с к о л ь к о в л о ж е н н ы х л о г и ч е с к и х п р о ц е с с о в , н а з ы в а е м ы х доменами приложений cation d o m a i n s ) . У п р а в л я е м ы е потоки С# (т. е. н а х о д я щ и е с я под у п р а в л е н и е м Microsoft work) могут р а б о т а т ь в р а м к а х о д н о г о или н е с к о л ь к и х д о м е н о в п р и л о ж е н и й и принад¬ л е ж а т ь при этом о д н о м у с и с т е м н о м у процессу. При з а п у с к е л ю б о г о п р и л о ж е н и я С# вначале с о з д а е т с я один п о т о к и с п о л н е н и я , ра¬ б о т а ю щ и й в р а м к а х д о м е н а п р и л о ж е н и я . П р и н е о б х о д и м о с т и п р о г р а м м н ы й к о д , ис¬ п о л н я е м ы й в р а м к а х о д н о г о д о м е н а п р и л о ж е н и я , м о ж е т создавать д о п о л н и т е л ь н ы е потоки и д о п о л н и т е л ь н ы е д о м е н ы п р и л о ж е н и й . П о э т о м у у п р а в л я е м ы е п о т о к и С# мо¬ гут пересекать г р а н и ц ы д о м е н о в п р и л о ж е н и я , если, к о н е ч н о , они о с т а ю т с я в п р е д е л а х о д н о г о с и с т е м н о г о процесса. Это п о з в о л я е т и с п о л ь з о в а т ь один и тот же п о т о к в рам¬ ках н е с к о л ь к и х д о м е н о в п р и л о ж е н и й .
Примеры многопоточных программ Чтобы использовать многопоточность, обычные программы, составленные на таких я з ы к а х п р о г р а м м и р о в а н и я , как С + + или P a s c a l , о б р а щ а ю т с я к п р о г р а м м н о м у и н т е р фейсу О С . Ч т о же касается п р о г р а м м С # , то в них м н о г о п о т о ч н о с т ь р е а л и з у е т с я с п о м о щ ь ю н а б о р а к л а с с о в , в х о д я щ и х в б и б л и о т е к у классов Microsoft Framework.
Создание и запуск потока класса Thread В нашей первой м н о г о п о т о ч н о й п р о г р а м м е мы будем р а б о т а т ь с к л а с с о м t e m . T h r e a d , о б ъ я в л е н н ы м в п р о с т р а н с т в е имен S y s t e m . T h r e a d i n g . И с х о д н ы й текст п р о г р а м м ы очень прост и п р е д с т а в л е н в л и с т и н г е 10.1. Листинг using using
10.1.
Файл
System;
namespace ThreadDemoApp public
static
void поток
Sys
static
void
Thread
args)
thr
myThreadDelegate new
new
потока
Прежде
всего
обратите
внимание
на
использование
пространства
имен
Sys
метод
Main,
tem
М ы п о д к л ю ч и л и его с п о м о щ ь ю к л ю ч е в о г о слова u s i n g . В
главном
классе
приложения
где
находится
мы о б ъ я в и л и с т а т и ч е с к и й м е т о д M y T h r e a d , к о т о р ы й будет р а б о т а т ь в рамках отдель¬ ного потока: static
void поток
Е д и н с т в е н н а я задача м е т о д а M y T h r e a d — в ы в о д на к о н с о л ь т е к с т о в о й с т р о к и . Что же касается м е т о д а M a i n , то он з а п у с к а е т м е т о д M y T h r e a d в о т д е л ь н о м пото¬ ке, о т о б р а ж а я п е р е д этим с о о б щ е н и е н а к о н с о л и , д о ж и д а е т с я ввода п р о и з в о л ь н о й с т р о к и с к л а в и а т у р ы , после чего з а в е р ш а е т с в о ю работу: ThreadStart Thread thr
new
T h r e a d S t a r t (MyThread)
new потока
П о с л е з а п у с к а этой п р о г р а м м ы н а к о н с о л и о т о б р а ж а ю т с я с л е д у ю щ и е строки: З а п у с к п о т о к а MyThread MyThread: поток запущен К а к все это р а б о т а е т ? П о с м о т р е в в н и м а т е л ь н о н а и с х о д н ы й текст метода M a i n , м о ж н о что в нем нет п р я м о г о в ы з о в а м е т о д а M y T h r e a d . В м е с т о этого для м е т о д а M y T h r e a d соз¬ д а е т с я так н а з ы в а е м ы й делегат m y T h r e a d D e l e g a t e типа T h r e a d S t a r t (о т о м , что это т а к о е , мы р а с с к а ж е м чуть п о з ж е ) : ThreadStart
new А В
Г. В.
Самоучитель
Конструктору T h r e a d s t a r t
имя метода, и с п о л ь з у е м о г о для с о з д а н и я
делегата. Д а л е е с п о м о щ ь ю этого делегата создается п о т о к класса T h r e a d : Thread thr
new
И н а к о н е ц , с о з д а н н ы й поток запускается при п о м о щ и метода S t a r t , о п р е д е л е н н о го в классе T h r e a d :
Сразу п о с л е в ы з о в а метода S t a r t н а ч н е т с я р а б о т а метода M y T h r e a d , о чем мож¬ но судить по с о о б щ е н и я м , п о я в л я ю щ и м с я на к о н с о л и . З а м е т и м , что о с н о в н а я про¬ г р а м м а и с о з д а н н ы й ей д о п о л н и т е л ь н ы й п о т о к р а б о т а ю т о д н о в р е м е н н о и с о в е р ш е н н о н е з а в и с и м о д р у г от друга. П р е ж д е чем м ы п р о д о л ж и м д а л ь н е й ш е е и з у ч е н и е м н о г о п о т о ч н о с т и , н е о б х о д и м о сделать н е к о т о р ы е р а з ъ я с н е н и я п о поводу н а з н а ч е н и я д е л е г а т о в , я в л я ю щ и х с я о д н о й из о с о б е н н о с т е й я з ы к а С#.
Использование делегатов К о г д а мы р а с с к а з ы в а л и о р а з м е щ е н и и п е р е м е н н ы х в о п е р а т и в н о й памяти к о м п ь ю т е р а , то г о в о р и л и , что каждая такая п е р е м е н н а я и м е е т свой адрес и р а з м е р . И с п о л ь з у я меха¬ низм у к а з а т е л е й , п р о г р а м м ы С++ могут н а п р я м у ю а д р е с о в а т ь с я к о б л а с т я м оператив¬ ной п а м я т и , в ы д е л е н н ы м для п е р е м е н н ы х . Аналогичная возможность прямой адресации имеется в языке С++ для функций. Ф у н к ц и и и м е т о д ы т о ж е р а з м е щ а ю т с я в о п е р а т и в н о й памяти по о п р е д е л е н н ы м адре¬ сам. З а п и с а в эти адреса в п е р е м е н н ы е , н а з ы в а е м ы е у к а з а т е л я м и на ф у н к ц и и или мето¬ д ы , п р о г р а м м ы С++ могут п е р е д а в а т ь и м у п р а в л е н и е . Д а л е е , п р о г р а м м ы С++ могут п е р е д а в а т ь у к а з а т е л и н а ф у н к ц и и д р у г и м ф у н к ц и я м и м е т о д а м . П р и этом ф у н к ц и я , и с п о л ь з у я п о л у ч е н н ы й у к а з а т е л ь на д р у г у ю ф у н к ц и ю , м о ж е т е е в ы з в а т ь . Ф у н к ц и и , в ы з ы в а е м ы е п о д о б н ы м о б р а з о м ч е р е з у к а з а т е л и , называ¬ ются функциями обратного вызова. Вы т а к ж е з н а е т е , что н е п р а в и л ь н о е п р и м е н е н и е у к а з а т е л е й м о ж е т привести к появ¬ лению трудно обнаруживаемых ошибок. В самом если п е р е д и с п о л ь з о в а н и е м со¬ д е р ж и м о е у к а з а т е л я на п е р е м е н н у ю или ф у н к ц и ю будет задано н е п р а в и л ь н о , послед¬ ствия о к а ж у т с я н е п р е д с к а з у е м ы м и . В н е м а л о й степени из-за этого о б с т о я т е л ь с т в а раз¬ р а б о т ч и к и С# отказались от и с п о л ь з о в а н и я у к а з а т е л е й . Тем не менее п о т р е б н о с т ь в ф у н к ц и я х о б р а т н о г о в ы з о в а при и с п о л ь з о в а н и и м н о г о п о т о ч н о с т и н у ж н о к а к и м - т о о б р а з о м F r a m e w o r k м е т о д , к о т о р ы й будет работать в р а м к а х о т д е л ь н о г о р а т н о г о в ы з о в а н е о б х о д и м ы и для о б р а б о т к и событий ( e v e n t s ) , рассказывать позже.
осталась. Например, указать с и с т е м е потока. Ф у н к ц и и об¬ о к о т о р ы х мы будем
Для р е а л и з а ц и и б е з о п а с н ы х указателей на м е т о д ы в я з ы к е С# был р а з р а б о т а н ме¬ х а н и з м д е л е г а т о в . В роли д е л е г а т а м о ж е т в ы с т у п а т ь с т а т и ч е с к и й м е т о д класса или статическое свойство. О б ы ч н о м е т о д - д е л е г а т о б ъ я в л я е т с я с п о м о щ ь ю к л ю ч е в о г о слова одна¬ ко в н а ш е м случае для р е а л и з а ц и и м н о г о п о т о ч н о с т и оно не п о т р е б о в а л о с ь : Глава 10. Многопоточность
317
public
static
void поток
Мы с о з д а е м д е л е г а т из о б ы ч н о г о с т а т и ч е с к о г о м е т о д а , п о л ь з у я с ь для этого классом ThreadStart: ThreadStart
new
П о с л е в ы п о л н е н и я этой строки в п е р е м е н н у ю m y T h r e a d D e l e g a t e будет записан с с ы л к а на м е т о д M y T h r e a d . Эта ссылка р е а л и з о в а н а без п р и м е н е н и я ука¬ зателей и п о т о м у б е з о п а с н а в и с п о л ь з о в а н и и . Она м о ж е т с с ы л а т ь с я (или, если х о т и т е , « у к а з ы в а т ь » ) т о л ь к о н а реально с у щ е с т в у ю щ и й о б ъ е к т класса T h r e a d S t a r t . По
своему назначению
метод
M y T h r e a d является
функцией
обратного
вызова.
Н а ш а п р о г р а м м а (т. е. м е т о д M a i n ) н и к о г д а не в ы з ы в а е т этот м е т о д н а п р я м у ю . В м е с т о этого она с о з д а е т для метода M y T h r e a d д е л е г а т m y T h r e a d D e l e g a t e , а затем пере¬ дает этот д е л е г а т к о н с т р у к т о р у класса T h r e a d : Thread
thr
new
К о н с т р у к т о р класса T h r e a d создает н о в ы й п о т о к , а наша п р о г р а м м а з а п и с ы в а е т с с ы л к у на этот п о т о к в п е р е м е н н у ю t h r . В д а л ь н е й ш е м с п о м о щ ь ю этой ссылки про¬ г р а м м а м о ж е т у п р а в л я т ь с о з д а н н ы м п о т о к о м . Для запуска п о т о к а на в ы п о л н е н и е , на¬ п р и м е р , и с п о л ь з у е т с я м е т о д S t a r t , о п р е д е л е н н ы й в классе T h r e a d :
Модели многопоточности В н и м а т е л ь н ы й ч и т а т е л ь з а м е т и т , ч т о , п о м и м о д е л е г а т о в , мы и с п о л ь з о в а л и в преды¬ д у щ е й п р о г р а м м е еще одну н о в у ю к о н с т р у к ц и ю С # , а и м е н н о атрибут S T A T h r e a d , з а д а ю щ и й так н а з ы в а е м у ю м о д е л ь м н о г о п о т о ч н о с т и Single Thread Apart¬ ments ( S T A ) : static
void
args)
Если г о в о р и т ь у п р о щ е н н о , т о а т р и б у т ы я з ы к а С # п о з в о л я ю т о п р е д е л и т ь характери¬ стики о б ъ е к т о в , перед к о т о р ы м и они р а с п о л о ж е н ы . В д а н н о м случае а т р и б у т S T A T h r e a d относится к методу M a i n , определяя модель многопоточности, в которой он б у д е т р а б о т а т ь при запуске п р о г р а м м ы . П р о г р а м м и с т ы п о з н а к о м и л и с ь с м о д е л я м и м н о г о п о т о ч н о с т и , когда к о м п а н и я M i п р и с т у п и л а к в н е д р е н и ю в п р а к т и к у своей м о д е л и к о м п о н е н т н ы х о б ъ е к т о в ( C o m p o n e n t Object а также технологии ActiveX. Детальное описание этих т е х н о л о г и й в ы х о д и т з а р а м к и нашей к н и г и . Ч и т а т е л е й , и н т е р е с у ю щ и х с я д а н н ы м в о п р о с о м , мы о т с ы л а е м к и П р и м е р ы с о з д а н и я к о м п о н е н т о в A c t i v e X для W e b мы о п и с а л и в 318
А В
Г.
Фролов. Язык
Самоучитель
Говоря к р а т к о , р е а л и з а ц и я м н о г о п о т о ч н о с т и в к о м п о н е н т а х
и ActiveX, реали
зующих визуальный пользовательский интерфейс,
учета ряда о с о б е н н о с т е й .
Это,
с применением которого
например,
н а л и ч и е м е х а н и з м а обмена с о о б щ е н и я м и ,
р е а л и з о в а н п о л ь з о в а т е л ь с к и й и н т е р ф е й с с т а н д а р т н ы х п р и л о ж е н и й Microsoft W i n d o w s . С целью обеспечения передачи данных между потоками различных компонентов и с и н х р о н и з а ц и и их
работы
был создан
м е х а н и з м разделов,
или
апартаментов (apart¬
m e n t s ) . В рамках этого м е х а н и з м а были о п р е д е л е н ы р а з л и ч н ы е м о д е л и м н о г о п о т о ч н о сти, о т л и ч а ю щ и е с я д р у г от д р у г а с п о с о б о м в з а и м о д е й с т в и я к о м п о н е н т о в , а т а к ж е спо¬ собом с и н х р о н и з а ц и и их р а б о т ы : •
модель разделенных
•
модель
свободных
потоков потоков
(apartment (free
threading);
threading).
Если и с п о л ь з у е т с я м о д е л ь р а з д е л е н н ы х п о т о к о в , то заботу о с и н х р о н и з а ц и и в ы з о B O B к о м п о н е н т о в берет на себя с и с т е м а то п р о б л е м ы
синхронизации
и
а если м о д е л ь с в о б о д н ы х п о т о к о в —
обеспечения передачи данных между компонентами
л о ж а т с я на сами эти к о м п о н е н т ы . Модель разделенных потоков, в свою
очередь,
•
однопоточную модель
apartment,
•
многопоточную модель
(single-threaded (multi-threaded
ч л е н и т с я на две п о д м о д е л и : STA);
apartments,
МТА).
В о з в р а щ а я с ь к п р о г р а м м а м С # , з а м е т и м , что они делятся на к о н с о л ь н ы е програм¬ мы (к ним о т н о с я т с я все п р о г р а м м ы , п р и в е д е н н ы е в нашей к н и г е ) , а т а к ж е на про¬ г р а м м ы с г р а ф и ч е с к и м п о л ь з о в а т е л ь с к и м и н т е р ф е й с о м . П о с л е д н и е из них с о з д а ю т с я с п р и м е н е н и е м к л а с с о в W i n d o w s F o r m s , в х о д я щ и х в состав Microsoft
Framework.
Так вот, п р о г р а м м ы С# с г р а ф и ч е с к и м п о л ь з о в а т е л ь с к и м и н т е р ф е й с о м , с о з д а н н ы е на
базе
классов
Windows
Forms, должны
работать
в
однопоточной
модели
STA.
Это п р о и с х о д и т п о т о м у , что классы W i n d o w s F o r m s п о л ь з у ю т с я с т а н д а р т н ы м интер¬ ф е й с о м О С W i n d o w s , п р е д п о л а г а ю щ и м п р и м е н е н и е модели р а з д е л е н н ы х п о т о к о в . Что же касается д р у г и х п р о г р а м м С # , то они м о г у т создаваться на базе м о д е л и с в о бодных К о г д а вы с о з д а е т е новый п р о е к т С# при п о м о щ и в и з у а л ь н о г о средства р а з р а б о т к и п р о г р а м м Microsoft Visual определение атрибута
Studio
то мастер проекта а в т о м а т и ч е с к и д о б а в л я е т перед о б ъ я в л е н и е м метода M a i n .
В предыдущих
п р и м е р а х п р о г р а м м мы и г н о р и р о в а л и этот а т р и б у т , так как для о д н о п о т о ч н ы х кон¬ с о л ь н ы х п р о г р а м м его н а л и ч и е н е с у щ е с т в е н н о .
Завершение работы созданного потока В п р е д ы д у щ е м п р и м е р е п р о г р а м м ы мы создавали поток, к о т о р ы й в ы в о д и л с о о б щ е н и е на к о н с о л ь , после чего сразу же з а в е р ш а л с в о ю работу. Р е а л ь н о , как мы у ж е г о в о р и л и , в отдельном потоке обычно выполняется какая-либо длительная процедура. В с л е д у ю щ е й м н о г о п о т о ч н о й п р о г р а м м е ( л и с т и н г 10.2) мы п р и в е д е м п о д о б н ы й
Глава
Многопоточность
Листинг using using
10.2.
Файл
System;
namespace class
LoopThreadApp
static
bool
stopThread;
public
static i
void
MyThread i+ + )
Console. WriteLine
i)
MyThread
static
void
args)
ThreadStart Thread thr
myThreadDelegate new
new
T h r e a d S t a r t (MyThread)
потока stopThread thr. string do
false;
str;
(x str поток: stopThread
true;
М е т о д , п р е д н а з н а ч е н н ы й для р а б о т ы в о т д е л ь н о м п о т о к е , с о д е р ж и т внутри себя б е с к о н е ч н ы й ц и к л , о т о б р а ж а ю щ и й на консоли п о с т о я н н о в о з р а с т а ю щ е е з н а ч е н и е пе¬ ременной
320
Фролов, Г. В. Фролов. Язык
Самоучитель
static
bool
public
static
stopThread; void
i
i++)
i)
MyThread
Условие выхода из р а в е н с т в о с о д е р ж и м о г о поля s t o p T h r e a d з н а ч е н и ю t r u e . З а м е т и м , о д н а к о , что м е т о д M y T h r e a d сам п о себе н е и з м е н я е т с о д е р ж и м о е этого п о л я , п о э т о м у без п о с т о р о н н е й цикл будет р а б о т а т ь б е с к о н е ч н о д о л г о (ну х о р о ш о до п е р е з а г р у з к и к о м п ь ю т е р а ) . Обратите также внимание на то, что внутри цикла мы вызываем статический метод T h r e a d . S l e e p . Он приостанавливает работу потока, в рамках которого работает метод M y T h r e a d , на 2000 П о т о к приостанавливается таким образом, чтобы не у м е н ь ш а т ь ресурсы п р о ц е с с о р а Р а с с м о т р и м т е п е р ь и с х о д н ы й т е к с т метода M a i n , у п р а в л я ю щ е г о р а б о т о й н а ш е г о потока. П р е ж д е всего этот м е т о д создает н а базе м е т о д а M y T h r e a d д е л е г а т , п е р е д а в а я его к о н с т р у к т о р у класса T h r e a d : ThreadStart Thread thr Далее
метод
new new запускает
поток
на
выполнение,
записывая
перед
этим
значение
f a l s e в поле s t o p T h r e a d : потока stopThread
false;
В р е з у л ь т а т е м е т о д M y T h r e a d будет з а п у щ е н как о т д е л ь н ы й п о т о к , р а б о т а ю щ и й п а р а л л е л ь н о с п о т о к о м , в рамках к о т о р о г о в ы п о л н я е т с я м е т о д M a i n нашей програм¬ мы. О работе этого п о т о к а м о ж н о с у д и т ь п о с т р о к а м вида
появляющим
ся на к о н с о л и , где х — п о с т о я н н о в о з р а с т а ю щ е е ч и с л о : Запуск потока Команда (х 0 1
MyThread выход)
Глава 10. Многопоточность Язык
Самоучитель
321
П о с л е з а п у с к а п о т о к а для метода M y T h r e a d метод M a i n н е з а в е р ш а е т с в о ю р а б о ту, а о р г а н и з у е т в ы п о л н е н и е цикла: string do
str;
(х str поток: X В этом ц и к л е п р о г р а м м а в в о д и т т е к с т о в ы е к о м а н д ы с к л а в и а т у р ы , о т о б р а ж а я их на к о н с о л и в с л е д у ю щ е м виде: основной
поток:
<команда>
У с л о в и е з а в е р ш е н и я этого цикла з а п и с ы в а е т в поле
в в о д строки q. В этом случае н а ш а п р о г р а м м а
s t o p T h r e a d значение
true,
что п р и в о д и т к о с т а н о в к е п о т о к а
MyThread: stopThread
true;
Т а к и м о б р а з о м , после з а п у с к а п р о г р а м м ы з а п р а в о в ы в е с т и с о о б щ е н и я н а к о н с о л ь к о н к у р и р у ю т д в а потока: З а п у с к п о т о к а MyThread Команда (х — 0 1 основной
поток:
Команда
(х
q
—
2 rMyThread:
3
основной
поток:
Команда
(х
ВЫХОД)
4 ret основной поток: ret Команда (х выход) 5 б 7 8 х основной поток: х Поток MyThread о с т а н о в л е н В р е з у л ь т а т е с о о б щ е н и я п е р е м е ш и в а ю т с я м е ж д у собой. П р и в в о д е к о м а н д ы х оба п о т о к а ( г л а в н ы й , с о з д а н н ы й для м е т о д а M a i n , а также п о т о к , з а п у щ е н н ы й м е т о д о м завершают свою работу.
322
Г.
Фролов.
Самоучитель
Потоки и классы В п р е д ы д у щ и х п р и м е р а х п р о г р а м м мы з а п у с к а л и в о т д е л ь н о м п о т о к е статический ме¬ тод о с н о в н о г о класса п р и л о ж е н и я , т. е. того класса, в к о т о р о м о б ъ я в л е н м е т о д M a i n . О д н а к о в ряде случаев было бы у д о б н е е с о з д а в а т ь о б ъ е к т ы на базе классов С # , у кото¬ рых один или н е с к о л ь к о м е т о д о в р а б о т а л и бы в о т д е л ь н о м потоке.
Такие о б ъ е к т ы
могли бы «жить» своей ж и з н ь ю , р а б о т а я о д н о в р е м е н н о д р у г с д р у г о м , а т а к ж е с глав¬ ным потоком программы. И с х о д н ы й текст п о д о б н о й п р о г р а м м ы мы п р и в е л и в л и с т и н г е Листинг •using using
10.3.
10.3.
Файл
System;
namespace class
TwoTreads StringWriter
private private private private
string str; bool stopThread; ThreadStart Thread thr;
public
s)
str s; stopThread
public
false;
void
if
WriteThread
public
void
go
myThreadDelegate thr new
Глава
Многопоточность
new
323
public
void
stop()
stopThread
true;
[STAThread] static void
args)
sw2 Console для о с т а н о в к и
new new Потоки запущены.
Нажмите
Enter
В этой п р о г р а м м е м ы о п р е д е л и л и класс к о т о р о г о цикли¬ чески в ы в о д я т н а к о н с о л ь т е к с т о в у ю с т р о к у , п е р е д а н н у ю к о н с т р у к т о р у : StringWriter private private private private
string str; bool stopThread; ThreadStart Thread
public str stopThread
s)
false;
К о н с т р у к т о р с о х р а н я е т с т р о к у , п р е д н а з н а ч е н н у ю для ц и к л и ч е с к о г о в ы в о д а на кон¬ с о л ь , в поле класса
324
A
Г.
Фролов.
Самоучитель
ц и к л и ч е с к о г о в ы в о д а , п р е д н а з н а ч е н н а я д л я работы в о т д е л ь н о м п о т о к е , не имеет н и к а к и х о с о б е н н о с т е й , за и с к л ю ч е н и е м т о г о , что она теперь н е с т а т и ч е с к а я :
public
void
if (stopThread)
WriteThread Так как в своей п р о г р а м м е мы с о б и р а е м с я создавать н е с к о л ь к о р а з н ы х о б ъ е к т о в класса S t r i n g W r i t e r , то нам п р и ш л о с ь и з б а в и т ь с я от с т а т и ч е с к и х полей и м е т о д о в . Если б ы , н а п р и м е р , поле s t r было с т а т и ч е с к и м , все о б ъ е к т ы класса S t r i n g W r i t e r в ы в о д и л и бы на консоль одну и ту же строку. П р и ч и н а этого в т о м , что для всех т а к и х о б ъ е к т о в был бы создан е д и н с т в е н н ы й
с т а т и ч е с к о г о поля s t r .
Чтобы запустить в отдельном потоке метод W r i t e T h r e a d , объявленный
мы и с п о л ь з у е м м е т о д
в классе S t r i n g W r i t e r с л е д у ю щ и м о б р а з о м :
public void go myThreadDelegate thr
new
new
Этот метод создает делегат
для метода
том н а его базе создает п о т о к класса T h r e a d . метода
а по¬
Далее поток запускается при помощи
Start.
Метод
stop
класса
StringWriter
записывает значение
true
в
поле
stop-
Thread:
public
void
stopThread
true;
Это п р и в о д и т к т о м у , что поток метода W r i t e T h r e a d п р е к р а щ а е т с в о ю р а б о т у , так как п р е р ы в а е т с я б е с к о н е ч н ы й ц и к л , в этом м е т о д е . Р а с с м о т р и м теперь м е т о д Main. В этом м е т о д е мы создаем два о б ъ е к т а класса S t r i n g W r i t e r , п е р в ы й из к о т о р ы х будет в ы в о д и т ь на к о н с о л ь в б е с к о н е ч н о м ц и к л е с и м в о л а второй — с и м в о л
StringWriter StringWriter
sw2
new new
Глава 10. Многопоточность
325
О д н а к о д л я т о г о , чтобы наши о б ъ е к т ы з а р а б о т а л и , н у ж н о их не т о л ь к о но и з а п у с т и т ь п о т о к и на в ы п о л н е н и е . Мы д е л а е м это при п о м о щ и м е т о д а
объяв
л е н н о г о в классе S t r i n g W r i t e r :
Здесь мы с н а ч а л а «запускаем» п е р в ы й о б ъ е к т , а з а т е м , с з а д е р ж к о й 250 мс, второй. Д а л е е г л а в н ы й п о т о к п р о г р а м м ы (т. е. м е т о д M a i n ) п е р е х о д и т в с о с т о я н и е о ж и д а н и я ввода строки с к л а в и а т у р ы :
Если в этот м о м е н т нажать к л а в и ш у Enter, м е т о д M a i n о с т а н о в и т в ы в о д с и м в о л о в на
консоль,
вызвав
метод
stop
для
каждого
из
созданных
объектов
класса
StringWriter:
В о т что вы у в и д и т е на к о н с о л и : Потоки Поток Поток
запущены.
Нажмите
WriteThread WriteThread
Enter для
остановки
потоков.
остановлен остановлен
О б р а т и т е в н и м а н и е , что в этом п р и м е р е п р о г р а м м ы мы п о л н о с т ь ю скрыли от ме¬ тода M a i n тот факт, что в классе S t r i n g W r i t e r п р и м е н я ю т с я с р е д с т в а м н о г о п о т о ч н о с т и . Это у п р о щ а е т работу с о б ъ е к т а м и класса S t r i n g W r i t e r , д е л а е т код метода Main проще и понятнее.
Управление потоками И т а к , т е п е р ь мы н а у ч и л и с ь с о з д а в а т ь п о т о к и и з а п у с к а т ь их на в ы п о л н е н и е . Теперь мы р а с с м о т р и м с р е д с т в а у п р а в л е н и я п о т о к а м и более п о д р о б н о .
Аварийное завершение потока В предыдущих примерах программ нами потоки з а в е р ш а л и с ь , « у м и р а я естественной П о т о к о с т а н а в л и в а л с я в м о м е н т в ы х о д а из метода, на базе ко¬ т о р о г о этот п о т о к был с о з д а н . П о м и м о э т о г о , с у щ е с т в у е т и д р у г а я в о з м о ж н о с т ь . З а п у щ е н н ы й п о т о к м о ж н о за¬ в е р ш и т ь а в а р и й н о , вызвав для э т о г о м е т о д A b o r t , о п р е д е л е н н ы й в классе T h r e a d . П р и этом в о з н и к н е т и с к л ю ч е н и е в результате н о р м а л ь н а я р а б о т а п о т о к а будет п р е р в а н а . А в а р и й н а я о с т а н о в к а п о т о к о в д е м о н с т р и р у е т с я в п р о г р а м м е , и с х о д н ы й т е к с т кото рой п р и в е д е н в л и с т и н г е 10.4.
326
А В Фролов, Г. В. Фролов. Язык
Самоучитель
Листинг
10.4.
using using
Файл
System;
namespace class
AbortThread StringWriter
private private private private
string bool stopThread; ThreadStart Thread t h r ;
public
s)
stopThread
public
false;
void
WriteThread ex)
"Аварийная
public
void
потока
go
myThreadDelegate thr new
Глава
остановка
Многопоточность
new
327
public
void
stop()
stopThread
public
class
true;
void
AbortThreadApp
static
void
StringWriter StringWriter
args) swl sw2
new new запущены.
для
аварийной
Нажмите
Enter
остановки
О б р а т и т е в н и м а н и я на и з м е н е н и я , к о т о р ы е мы внесли в и с х о д н ы й т е к с т метода по с р а в н е н и ю с п р е д ы д у щ е й п р о г р а м м о й : public
void
try
328
А В Фролов, Г.
Фролов. Язык
Самоучитель
WriteThread ex)
остановка
потока
Теперь все свои д е й с т в и я п о т о к в ы п о л н я е т внутри блока t r y . Когда р о д и т е л ь с к и й поток (т. е. п о т о к , с о з д а в ш и й д о ч е р н и й поток на базе м е т о д а з а в е р ш а е т р а б о т у н а ш е г о п о т о к а а в а р и й н о , т о , как мы т о л ь к о что го¬ ворили, возникает исключение Н а ш м е т о д п е р е х в а т ы в а е т это и с к л ю ч е н и е , в ы д а в а я н а консоль с о о т в е т с т в у ю щ и е сообщения: Потоки
запущены.
Нажмите
E n t e r для
аварийной
остановки
потоков.
Thread was aborted. at at
in
Аварийная о с т а н о в к а Исключение: aborted. at System at
потока
Аварийная
потока
c#
28 WriteThread Thread was Sleep (Int32
остановка
being
being
millisecondsTimeout) in c#
28 WriteThread
Зачем н у ж н о п е р е х в а т ы в а т ь и с к л ю ч е н и е при а в а р и й н о м з а в е р ш е н и и п о т о к а ? Дело в т о м , что внутри п о т о к а могут и с п о л ь з о в а т ь с я р а з л и ч н ы е р е с у р с ы , требую¬ щие я в н о г о о с в о б о ж д е н и я . Э т о такие р е с у р с ы , н а п р и м е р , как о т к р ы т ы е ф а й л ы , базы д а н н ы х , с е т е в ы е с о е д и н е н и я и т. п. Х о т я с и с т е м а с б о р к и м у с о р а , в с т р о е н н а я в среду и с п о л н е н и я п р о г р а м м С # , а в т о м а т и ч е с к и о с в о б о ж д а е т н е н у ж н ы е более блоки опера¬ т и в н о й п а м я т и , она не в с о с т о я н и и а в т о м а т и ч е с к и з а к р ы т ь файл или базу д а н н ы х . В каком блоке л у ч ш е о с в о б о ж д а т ь р е с у р с ы : в блоке Блок
или в блоке
c a t c h н у ж н о и с п о л ь з о в а т ь в том с л у ч а е , когда п о т о к п о к а к о й - т о п р и ч и н е
не м о ж е т н е м е д л е н н о з а в е р ш и т ь с в о ю р а б о т у . В этом случае он д о л ж е н о т м е н и т ь ава¬ р и й н о е з а в е р ш е н и е , в ы з в а в метод Если
не
вызывать
метод
исключения Глава 10. Многопоточность
ResetAbort. ResetAbort в
блоке
c a t c h при
обработке
это и с к л ю ч е н и е в о з н и к н е т п о в т о р н о .
329
Что же о с в о б о ж д е н и я р е с у р с о в при а в а р и й н о м з а в е р ш е н и и п о т о к а , то для этого л у ч ш е всего и с п о л ь з о в а т ь блок З а м е т и м , что а в а р и й н о е з а в е р ш е н и е р а б о т ы потока д о л ж н о и с п о л ь з о в а т ь с я только при н е о б х о д и м о с т и . В н о р м а л ь н о м р е ж и м е работы п о т о к и д о л ж н ы з а в е р ш а т ь с я сами, н а п р и м е р , как в п р о г р а м м е , и с х о д н ы й текст к о т о р о й был п р и в е д е н в л и с т и н г е 10.3.
Пауза в работе потока В п р е д ы д у щ и х п р и м е р а х п р о г р а м м мы в ы з ы в а л и м е т о д S l e e p для п р и о с т а н о в к и ра¬ боты п о т о к а на к а к о е - т о в р е м я . В классе T h r e a d имеется два п е р е г р у ж е н н ы х м е т о д а S l e e p . П е р в ы й и з этих ме¬ т о д о в п р и н и м а е т в качестве е д и н с т в е н н о г о а р г у м е н т а з н а ч е н и е и н т е р в а л а з а д е р ж к и в м и л л и с е к у н д а х , а в т о р о й — с с ы л к у на о б ъ е к т класса T i m e S p a n . Класс у д о б е н для ф о р м а т н о г о и н т е р в а л а в р е м е н и . В нем предусмотре¬ но н е с к о л ь к о к о н с т р у к т о р о в : public public public public
TimeSpan ( i n t , TimeSpan ( i n t , TimeSpan
int, int, int,
int, int,
int); int,
П е р в ы й из этих к о н с т р у к т о р о в п о з в о л я е т з а д а в а т ь п е р и о д в р е м е н и в и н т е р в а л а х р а б о т ы с и с т е м н о г о т а й м е р а (100 С п о м о щ ь ю в т о р о г о м о ж н о задавать и н т е р в а л в часах, м и н у т а х и с е к у н д а х . Т р е т и й п о з в о л я е т у к а з ы в а т ь к о л и ч е с т в о д н е й , ч а с о в , м и н у т и секунд, а четвертый — количество дней, часов, минут, секунд и миллисекунд. Ниже мы показали пример использования второго варианта перегруженного мето да S l e e p для з а д е р ж к и п о т о к а на 2 ч, 1 мин и 10 с: int hour i n t int sec
2; 10; min,
П р и н е о б х о д и м о с т и вы м о ж е т е о с т а н о в и т ь р а б о т у п о т о к а н а в с е г д а , указав в каче¬ стве и н т е р в а л а з а д е р ж к и м е т о д у константу out
что
Если работа метода остановлена указанным выше способом, то единственное, м о ж н о с д е л а т ь е п о т о к о м , — это з а в е р ш и т ь его р а б о т у а в а р и й н о м е т о д о м Р а с с м о т р и м п р о г р а м м у , и с х о д н ы й текст к о т о р о й п р е д с т а в л е н в л и с т и н г е 10.5.
Листинг
10.5.
Файл
using using
System;
class
StringWriter
private private
string Thread
thr; A
Г.
Фролов.
Самоучитель
public
s)
Str public
void
TimeSpan(0,
ex)
public
void
thr
new
public
ThreadStart (WriteThread)
void
namespace class
Thread(new
Timeout TimeoutApp
static
void
S t r i n g W r i t e r sw
args) new Enter
Глава 10. Многопоточность
для
остановки
331
метод public
в р а м к а х о т д е л ь н о г о потока:
void
try
ex)
П о л у ч и в у п р а в л е н и е , м е т о д W r i t e T h r e a d в ы в о д и т с о о б щ е н и е н а к о н с о л ь , а по том о с т а н а в л и в а е т с в о ю работу н а б е с к о н е ч н ы й п е р и о д в р е м е н и . Что же к а с а е т с я м е т о д а M a i n , то он з а п у с к а е т п о т о к на в ы п о л н е н и е , а п о т о м пре¬ р ы в а е т его р а б о т у в ы з о в о м метода a b o r t : StringWriter
new
StringWriter Enter
для
остановки
В р е з у л ь т а т е на к о н с о л и п о я в л я е т с я с о о б щ е н и е о в о з н и к н о в е н и и у ж е и з в е с т н о г о вам и с к л ю ч е н и я Нажмите E n t e r д л я о с т а н о в к и п о т о к а . Поток з а п у щ е н Thread was at at
in
being
c#
22 Таким о б р а з о м , п о т о к , п р и о с т а н о в и в ш и й с в о ю работу на н е к о т о р о е время (или на¬ в с е г д а ) , м о ж н о п р е р в а т ь . Если п р е д у с м о т р е т ь с о о т в е т с т в у ю щ и й о б р а б о т ч и к исключе¬ ния, этот п о т о к с м о ж е т в ы п о л н и т ь перед з а в е р ш е н и е м своей р а б о т ы к а к и е - л и б о фи¬ н а л ь н ы е д е й с т в и я , н а п р и м е р о с в о б о д и т ь н е н у ж н ы е более р е с у р с ы , з а к р ы т ь о т к р ы т ы е ранее ф а й л ы , базы д а н н ы х и т. п. в н и м а н и е еще на одно н о в ш е с т в о в нашей п р о г р а м м е . Оно касается ме¬ тода 332
класса
StringWriter: А
Г.
Фролов. Язык
Самоучитель
public
void
go
thr new thr Start( Так как д е л е г а т , с о з д а н н ы й
нами н а базе м е т о д а W r i t e T h r e a d ,
нужен т о л ь к о
один раз для с о з д а н и я нового о б ъ е к т а класса T h r e a d , м ы н е м н о г о у п р о с т и л и класс S t r i n g W r i t e r и метод W r i t e T h r e a d . У п р о щ е н и е з а к л ю ч а е т с я в т о м , что мы те¬ перь не х р а н и м д е л е г а т в о т д е л ь н о м поле класса, а сразу после создания п е р е д а е м его к о н с т р у к т о р у класса T h r e a d .
Приостановка и возобновление работы З а п у с т и в поток н а в ы п о л н е н и е , п р о г р а м м а м о ж е т в р е м е н н о п р и о с т а н о в и т ь его р а б о т у , а затем п р о д о л ж и т ь работу п о т о к а с п р е р в а н н о г о места. Для того чтобы п р и о с т а н о в и т ь п о т о к , н е о б х о д и м о и с п о л ь з о в а т ь м е т о д T h r e a d . S u s p e n d . П р о д о л ж е н и е работы п о т о к а в ы п о л н я е т с я м е т о д о м В л и с т и н г е 10.6 мы привели п р и м е р п р о г р а м м ы , д е м о н с т р и р у ю щ е й и с п о л ь з о в а н и е упомянутых выше методов. Листинг
10.6.
Файл
using using
System;
class
StringWriter
private private private
string bool Thread
str;
public
s)
s stopThread
false;
Thread. Sleep (500)
WriteThread
Глааа
остановлен")
333
public
void
thr
go
new
public
void
stopThread
public
void
public
void
public
void
true;
Resume
thr
StartStop
namespace class
StartStopApp
static
void
StringWriter StringWriter
args)
sw2
new new
запущены.
Нажмите E n t e r
для п р и о с т а н о в к и
Console для в о з о б н о в л е н и я
334
приостановлены.
Нажмите
Enter
работы
А. В. Фролов, Г. В Фролов. Язык
Самоучитель
Нажмите
Enter
для о с т а н о в к и
Для п р и о с т а н о в к и и п о с л е д у ю щ е г о в о з о б н о в л е н и я р а б о т ы п о т о к а мы п р е д у с м о т р е ¬ л и в классе S t r i n g W r i t e r два м е т о д а : public
public
Suspend
void
Сразу после запуска г л а в н ы й п о т о к нашей п р о г р а м м ы , р а б о т а ю щ и й в р а м к а х мето¬ да M a i n , создает два о б ъ е к т а класса S t r i n g W r i t e r и з а п у с к а е т в к а ж д о м из них по¬ токи, отображающие символы на консоли: StringWriter StringWriter
swl sw2
new new
Далее н а ш а п р о г р а м м а о ж и д а е т , пока п о л ь з о в а т е л ь не н а ж м е т к л а в и ш у Enter, после чего работа п о т о к о в п р и о с т а н а в л и в а е т с я : "Потоки
Глава
запущены.
Многопоточность
Нажмите
Enter
для
приостановки
335
Чтобы вновь в о з о б н о в и т ь работу п о т о к о в , н у ж н о снова н а ж а т ь к л а в и ш у Enter: возобновления
("Потоки работы
приостановлены.
Нажмите
Enter
для
И н а к о н е ц , н а ж а в на к л а в и ш у Enter в т р е т и й р а з , вы о к о н ч а т е л ь н о з а в е р ш и т е р а б о т у потоков методом s t o p : "Работа
Нажмите
Enter
для
остановки
Вот что п о я в и т с я на к о н с о л и : +Потоки
запущены.
-Потоки
E n t e r для
Нажмите
Работа Поток Поток
Нажмите
Нажмите WriteThread WriteThread
приостановки
Enter
Enter
для
для
потоков.
возобновления
остановки
работы
потоков.
остановлен остановлен
Таким о б р а з о м , т е п е р ь в ы з н а е т е , что п о т о к - р о д и т е л ь м о ж е т п р е р ы в а т ь , в р е м е н н о п р и о с т а н а в л и в а т ь и в о з о б н о в л я т ь р а б о т у с о з д а н н ы х им д о ч е р н и х п о т о к о в . Есть и еще одна в о з м о ж н о с т ь п о т о к а м и — и з м е н е н и е их п р и о р и т е т о в .
Управление приоритетами потоков Если п р о ц е с с с о з д а л н е с к о л ь к о п о т о к о в , то все они в ы п о л н я ю т с я п а р а л л е л ь н о , п р и ч е м время ц е н т р а л ь н о г о п р о ц е с с о р а (или н е с к о л ь к и х ц е н т р а л ь н ы х п р о ц е с с о р о в в мульти¬ процессорных системах) распределяется между этими задачами. в р е м е н и ц е н т р а л ь н о г о п р о ц е с с о р а з а н и м а е т с я с п е ц и а л ь н ы й мо¬ д у л ь ОС п л а н и р о в щ и к . П л а н и р о в щ и к по о ч е р е д и п е р е д а е т у п р а в л е н и е о т д е л ь н ы м п о т о к а м , так что д а ж е в о д н о п р о ц е с с о р н о й с и с т е м е создается п о л н а я и л л ю з и я парал¬ лельной работы запущенных потоков. Как м ы у ж е г о в о р и л и , при и с п о л ь з о в а н и и в ы т е с н я ю щ е й м н о г о п о т о ч н о с т и распре¬ д е л е н и е в р е м е н и в ы п о л н я е т с я п о п р е р ы в а н и я м с и с т е м н о г о таймера. П о э т о м у к а ж д о м у потоку д а е т с я о п р е д е л е н н ы й и н т е р в а л в р е м е н и , в т е ч е н и е к о т о р о г о он н а х о д и т с я в ак¬ тивном состоянии. З а м е т и м , что п л а н и р о в щ и к р а с п р е д е л я е т время для п о т о к о в , а не для п р о ц е с с о в . с о з д а н н ы е р а з н ы м и п р о ц е с с а м и , к о н к у р и р у ю т между собой з а п о л у ч е н и е п р о ц е с с о р н о г о в р е м е н и . В р а м к а х к а ж д о г о процесса м о ж е т с о з д а в а т ь с я один или не¬ с к о л ь к о потоков.
336
А В
Г. В. Фролов.
Самоучитель
К а к и м и м е н н о образом п р о и с х о д и т к о н к у р е н ц и я между п о т о к а м и з а п р о ц е с с о р н о е время? При п о м о щ и м е х а н и з м а приоритетов (priority). П р и л о ж е н и я С# могут у к а з ы в а т ь с л е д у ю щ и е значения для п р и о р и т е т о в о т д е л ь н ы х п о т о к о в : •
Highest,
• П о у м о л ч а н и ю вновь с о з д а н н ы й поток и м е е т н о р м а л ь н ы й п р и о р и т е т N o r m a l . Если о с т а л ь н ы е потоки в системе и м е ю т тот же самый п р и о р и т е т , то все потоки п о л ь з у ю т с я процессорным временем на равных правах. При необходимости вы можете повысить или понизить приоритет отдельных задач, определив для них другие значения приоритета. Потоки с п о в ы ш е н н ы м приоритетом ( A b o v e N o r m a l и H i g h e s t ) выполняются в первую очередь, а с п о н и ж е н н ы м ( B e l o w Normal и при отсутствии готовых к в ы п о л н е н и ю потоков, и м е ю щ и х более высокий приоритет. Зачем н у ж н о и з м е н я т ь п р и о р и т е т ы п о т о к о в ? Если ваша п р о г р а м м а в ы п о л н я е т к а к у ю - л и б о д л и т е л ь н у ю работу и о д н о в р е м е н н о ведет д и а л о г с п о л ь з о в а т е л е м , и м е е т смысл п о в ы с и т ь п р и о р и т е т потока, о т в е ч а ю щ е г о за такой д и а л о г . В п р о т и в н о м случае п о л ь з о в а т е л я будет р а з д р а ж а т ь з а м е д л е н н а я ре¬ акция п р о г р а м м ы на о п е р а ц и и , в ы п о л н я е м ы е при п о м о щ и м ы ш и и к л а в и а т у р ы . Длительные процессы лучше выполнять с низким приоритетом, чтобы они не мешали в ы п о л н е н и ю более важных и более срочных задач. В любом случае изменяйте приоритеты потоков только в том случае, когда это действительно необходимо. Теперь мы п о с м о т р и м на п р а к т и к е , как и з м е н е н и е п р и о р и т е т о в о т д е л ь н ы х п о т о к о в м о ж е т влиять на работу м н о г о п о т о ч н о й п р о г р а м м ы . В л и с т и н г е 10.7 мы п р и в е л и ис¬ х о д н ы й текст такой п р о г р а м м ы , з а п у с к а ю щ е й потоки с р а з н ы м и п р и о р и т е т а м и . Листинг using using
10.7.
Файл
System;
namespace class
Priority PriorityApp
public
static
void
i = 0;
Глава
i<100000;
i++)
337
public
static
void
i+ +
static
void
Main ( s t r i n g
T h r e a d hi Thread
В
главном
и
new new
классе
нашей
args)
Thread(new
программы мы
объявили
методы
первый из которых д о л ж е н б у д е т работать с высоким приорите
т о м , а второй — с низким: public
static
void
i = 0;
public
static
void
for(long
i = 0;
i+ + )
i+ +
Исходные тексты отличаются только символом, отображаемым на к о н с о л и . Для того чтобы э ф ф е к т и з м е н е н и я п р и о р и т е т о в стал з а м е т н е е , мы
338
Фролов, Г. В. Фролов. Язык
Самоучитель
чили в цикл о т о б р а ж е н и я с и м в о л о в п р о г р а м м н у ю з а д е р ж к у , р е а л и з о в а н н у ю с помо¬ щ ь ю ничего н е д е л а ю щ е г о цикла f o r ( l o n g i = 0;
i + + );
Почему м ы н е стали и с п о л ь з о в а т ь здесь метод T h r e a d . S l e e p ? Дело в т о м , что м е т о д S l e e p в ы п о л н я е т о ж и д а н и е таким с п о с о б о м , к о торый не нагружает центральный процессор компьютера. Иными словами, поток, вы полнение которого задерживается методом не уменьшает ресурсы центрального процессора. Но чтобы наша п р о г р а м м а в ы в о д и л а с и м в о л ы на к о н с о л ь не с л и ш к о м б ы с т р о , нам как раз н у ж н о загрузить ч е м - н и б у д ь ц е н т р а л ь н ы й п р о ц е с с о р . И м е н н о эту задачу и ре¬ шает б е с п о л е з н ы й н а вид цикл З а м е т и м , что для д о с т и ж е н и я н е о б х о д и м о г о эф¬ фекта н а вашем к о м п ь ю т е р е , в о з м о ж н о , п р и д е т с я и з м е н и т ь к о л и ч е с т в о п р о х о д о в э т о г о цикла. П е р е д тем как з а п у с т и т ь п о т о к и , мы задаем их п р и о р и т е т ы , и з м е н я я с в о й с т в о Priority: Thread Thread
hi
new new
Thread(new
П е р в о м у потоку п р и с в а и в а е т с я п р и о р и т е т , н е м н о г о п р е в ы ш а ю щ и й н о р м а л ь н ы й приоритет. Второй поток работает с нормальным приоритетом. П р о в е р я я работу этой п р о г р а м м ы , вы м о ж е т е провести э к с п е р и м е н т , и з м е н я я зна¬ ч е н и я п р и о р и т е т о в . Если п р и о р и т е т ы о д и н а к о в ы , н а к о н с о л и будет н а р и с о в а н о п р и одинаковое количество плюсов и минусов: + + + + + + + + +
I
+ +
+ + + + + + + + + +
Если же п р и о р и т е т п о т о к а , в ы в о д я щ е г о на к о н с о л ь п л ю с ы , будет б о л ь ш е , чем при¬ оритет потока, о т о б р а ж а ю щ е г о м и н у с ы , к а р т и н а п р и м е р с л е д у ю щ и й вид: + + + + + + + + + + + ++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Как в и д и т е , п л ю с ы т е п е р ь о т о б р а ж а ю т с я н а м н о г о чаще м и н у с о в . Глава
Многопоточность
339
Синхронизация потоков М н о г о п о т о ч н ы й р е ж и м р а б о т ы о т к р ы в а е т новые в о з м о ж н о с т и для п р о г р а м м и с т о в , од нако за эти в о з м о ж н о с т и п р и х о д и т с я р а с п л а ч и в а т ь с я у с л о ж н е н и е м п р о ц е с с а проекти¬ р о в а н и я п р и л о ж е н и я и о т л а д к и . О с н о в н а я т р у д н о с т ь , с которой с т а л к и в а ю т с я про¬ г р а м м и с т ы , р а н е е н и к о г д а не с о з д а в а в ш и е м н о г о п о т о ч н ы е п р о г р а м м ы , — это синхронизация одновременно работающих потоков. Для чего и к о г д а она н у ж н а ? О д н о п о т о ч н а я п р о г р а м м а , т а к а я , н а п р и м е р , как п р о г р а м м а M S - D O S , при запуске п о л у ч а е т в м о н о п о л ь н о е р а с п о р я ж е н и е все р е с у р с ы к о м п ь ю т е р а . Так как в о д н о п о т о ч ной ОС с у щ е с т в у е т т о л ь к о один п р о ц е с с , он и с п о л ь з у е т эти р е с у р с ы в той последова¬ т е л ь н о с т и , к о т о р а я с о о т в е т с т в у е т л о г и к е р а б о т ы п р о г р а м м ы . П р о ц е с с ы и з а д а ч и , рабо¬ тающие одновременно в многопоточной системе, обращаются одновременно к одним и тем же р е с у р с а м . Если при р а з р а б о т к е п р о г р а м м не у ч и т ы в а т ь это о б с т о я т е л ь с т в о , программы будут работать с ошибками. П о я с н и м это на п р о с т о м Пусть мы с о з д а е м п р о г р а м м у , в ы п о л н я ю щ у ю о п е р а ц и и с б а н к о в с к и м с ч е т о м . Опе¬ рация снятия н е к о т о р о й с у м м ы д е н е г со счета м о ж е т п р о и с х о д и т ь в такой последова¬ тельности: •
на п е р в о м ш а г е п р о в е р я е т с я о б щ а я с у м м а д е н е г , к о т о р а я х р а н и т с я на счету;
•
если эта с у м м а р а в н а или п р е в ы ш а е т размер с н и м а е м о й с у м м ы д е н е г , о б щ а я сумма уменьшается на необходимую величину;
•
з н а ч е н и е о с т а т к а з а п и с ы в а е т с я на т е к у щ и й счет.
Если о п е р а ц и я у м е н ь ш е н и я т е к у щ е г о счета в ы п о л н я е т с я в о д н о п о т о ч н о й то н и к а к и х п р о б л е м не в о з н и к н е т . О д н а к о п р е д с т а в и м себе, что два п р о ц е с с а тока) п ы т а ю т с я о д н о в р е м е н н о в ы п о л н и т ь т о л ь к о что о п и с а н н у ю о п е р а ц и ю и тем же с ч е т о м . П у с т ь при этом на счету н а х о д и т с я 5 млн. д о л л а р о в , а оба п ы т а ю т с я снять с него по 3 млн. д о л л а р о в . Допустим, события разворачиваются следующим образом:
системе, (или по¬ с одним процесса
•
п е р в ы й п р о ц е с с п р о в е р я е т с о с т о я н и е т е к у щ е г о счета и у б е ж д а е т с я , что на нем хра¬ 5
•
второй процесс проверяет состояние что на нем х р а н и т с я 5 млн. д о л л а р о в ;
•
п е р в ы й п р о ц е с с у м е н ь ш а е т счет на 3 млн. д о л л а р о в и з а п и с ы в а е т о с т а т о к (2 млн. долларов) на счет;
•
второй п р о ц е с с в ы п о л н я е т ту же с а м у ю о п е р а ц и ю , так как п о с л е п р о в е р к и с ч и т а е т , что на счету п о - п р е ж н е м у х р а н и т с я 5 млн. д о л л а р о в .
текущего
счета
и
также
убеждается,
В р е з у л ь т а т е п о л у ч и л о с ь , что со счета, на к о т о р о м н а х о д и л о с ь 5 м л н . д о л л а р о в , было снято 6 м л н . д о л л а р о в и при этом там осталось еще 2 млн. д о л л а р о в ! И т о г о банку нанесен у щ е р б в 3 млн. д о л л а р о в . Как же с о с т а в и т ь п р о г р а м м у у м е н ь ш е н и я счета, чтобы она не п о з в о л я л а в ы т в о р я т ь подобное?
340
А. В. Фролов, Г В. Фролов. Язык СМ. Самоучитель