MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_NextPart_01C6E59F.7229A350" Данный документ является веб-страницей в одном файле, также называемой файлом веб-архива. Если вы видите это сообщение, значит данный обозреватель или редактор не поддерживает файлы веб-архива. Загрузите обозреватель, поддерживающий веб-архивы, например Microsoft Internet Explorer. ------=_NextPart_01C6E59F.7229A350 Content-Location: file:///C:/64525993/file8483.htm Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset="windows-1251" Азы C#

    

Ca= tone.

Азы C#.

СОДЕРЖАНИЕ

  = ;  

Часть 1.

Гла­ва 2. Син­так­сис дек­ла­ра­ций.

Гла­ва 3. Син­так­сис дек­ла­ра­ций мас­си­вов, свойств, де­ле­га­тов. As­sembly.<= /a>

Гла­ва 4. Ос­нов­ные опе­ра­то­ры и кон­с­т­рук­ты.

Гла­ва 5. Ини­ци­али­за­ция пе­ре­мен­ных. Кол­лек­ции. Ти­пы дан­ных.

Гла­ва 6. При­мер ко­да.

Часть 2.

Гла­ва 2. Ге­не­ало­гия клас­сов.

Гла­ва 3. Ис­к­лю­че­ния.

Гла­ва 4. Муль­ти­по­точ­ность.

Гла­ва 5. Вы­зов фун­к­ций биб­ли­отек Win32 (P/Invo­ke)

Часть 3.

Гла­ва 2. Се­ри­али­за­ция.

Гла­ва 3. Ref­lec­ti­on.

Гла­ва 4. Са­мо­дель­ные эле­мен­ты уп­рав­ле­ния 1.

Гла­ва 5. Са­мо­дель­ные эле­мен­ты уп­рав­ле­ния 2.

Часть 4.

Гла­ва 2. Объ­ект Grap­hics.

Гла­ва 3. Цве­та.

Гла­ва 4. Ка­ран­да­ши.

Гла­ва 5. Кис­ти.

Гла­ва 6. Ге­омет­ри­чес­кие эле­мен­ты.

Гла­ва 7. Мат­рич­ные тран­с­фор­ма­ции.

Гла­ва 8. Гра­фи­чес­кие фай­лы.

Гла­ва 9. При­ме­ры.

При­ло­же­ния.

2. Са­мо­дель­ная го­ло­во­лом­ка.

3. Са­мо­дель­ный из­ме­ня­тель раз­ме­ра кар­ти­нок.

4. Сис­тем­ный ин­ди­ка­тор в за­го­лов­ке ок­на.

5. "Ошку­ри­ва­ние" прог­рам­мы.

6. Пе­рех­ват­чик всех оши­бок.

7. Са­мо­дель­ный бе­ка­пер.

  = ;  

  = ;  

Часть 1.

  = ;  Самые ос­нов­ные ве­щи в язы­ке C# и плат­фор­ме .NET вце­лом.

  = ;  

Глава 1. Как читать чужой код.

  &nb= sp; 

    По прось­бам чи­та­те­лей­, по= ­пы­тал­ся на­пи­сать об ос­но­вах язы­ка. Впро­чем, толь­ко прис­ту­пив по­нял, что д= е­ло гиб­лое - что­бы пи­сать об ос­но­вах на­до быть или МУД­рым АКа­де­ми­кОМ,= или дей­ст­ви­тель­но иметь та­лант пре­по­да­ва­те­ля. Я та­ко­во­го та­лан­та= не имею. По­это­му смо­гу толь­ко крат­ко рас­ска­зать о не­ко­то­рых осо­бен­= нос­тях. По­няв, что от та­ко­го тол­ку ма­ло, я ре­шил на­пи­сать для на­ча­ла как = чи­тать чу­жой код. Ибо, нас­коль­ко я по­ни­маю, ос­нов­ные проб­ле­мы с по­ни­ма­= ни­ем ко­да кро­ют­ся не в нез­на­нии, а в не­уме­нии эти са­мые зна­ния до­быть.= В этой об­лас­ти пи­сать про­ще, т.к. мне час­то при­хо­дит­ся чи­тать чу­жой код, са­мо­го раз­но­го уров­ня и на са­мых раз­ных, в том чис­ле не­из­вес= ­т­ных мне, язы­ках.

    

    Кратко о язы­ке

    Итак язык C# вы­рос из C++, со= х­ра­нив ос­нов­ные осо­бен­нос­ти син­так­си­са и опе­ра­то­ры. Да и ос­нов­ные опе= ­ра­то­ры оди­на­ко­вы во всех язы­ках, так что ес­ли вы зна­ете хоть один язык, то у= вас не дол­ж­но воз­ни­кать воп­ро­са что та­кое for, if...then...else, do...wh= i­le и пр. Не са­мый рас­п­рос­т­ра­нен­ный опе­ра­тор ис­поль­зу­ющий­ся в C# -= fo­re­ach. Соб­с­т­вен­но де­ла­ет то, что обоз­на­ча­ет - "for each =3D для каж­= до­го", т.е. про­во­дит цикл для каж­до­го чле­на из мас­си­ва/кол­лек­ции/спис­ка.=

    Особенностью всех С-по­доб­ных= язы­ков яв­ля­ет­ся ог­ра­ни­чи­ва­ние бло­ков ко­да фи­гур­ны­ми скоб­ка­ми, в от­= ли­чии от, нап­ри­мер, Ba­sic, в ко­то­ром все окон­ча­ния обоз­на­ча­ют­ся как End что-то (Sub, If и пр.). Ну и еще од­на осо­бен­ность - лю­бовь к сок­ра­ще­= ни­ям все­го че­го толь­ко мож­но.

    В .NET вце­лом по­яви­лось нес= ­коль­ко осо­бен­нос­тей - нап­ри­мер, из­чез­ли ос­нов­ные ти­пы дан­ных. Те­перь к= лю­че­вые сло­ва do­ub­le и int не пред­с­тав­ля­ют со­бой пос­ле­до­ва­тель­ность ба= йт в па­мя­ти, а яв­ля­ют­ся крат­кой за­писью для System.Do­ub­le и System.Int32 (на 32-х раз­ряд­ных ма­ши­нах) со­от­вет­с­т­вен­но. По­яви­лись ин­тер­фе= й­сы, поз­во­ля­ющие опи­сать на­бор фун­к­ций­, не­об­хо­ди­мых для сов­мес­ти­м= ос­ти с ка­ким-ли­бо клас­сом, и еще мно­го че­го, в ос­нов­ном не нуж­но­го но­в= ич­кам.

    И не на­до за­бы­вать, что в .= NET сох­ра­не­на сис­те­ма win­dows со­об­ще­ний и со­бы­тий. Так что иметь пре= д­с­тав­ле­ние о том, как ра­бо­та­ет Win­dows опе­ра­ци­он­ка не­об­хо­ди­мо хо­тя бы на = на­чаль­ном уров­не.

    

    Основы ра­бо­ты сис­те­мы

    Специалистов за­ра­нее про­шу = не кри­вит­ся - я бу­ду пи­сать толь­ко о са­мых ос­но­вах и прос­тым язы­ком. То что нас= ин­те­ре­су­ет - это сис­те­ма со­бы­тий (event) и их кон­т­ро­ля. Что бы ни про­изош­ло в= ком­пе - дер­ну­лась мыш­ка, на­жа­ли кла­ви­шу или от­к­ры­ли ок­но - это прев­ра= ­ща­ет­ся в event. Event'ы бы­ва­ют раз­ные - на все слу­чаи жиз­ни. При же­ла­нии вам ник­то не ме­ша­ет сде­ла­ет свое со­бы­тие, впро­чем это до­воль­но ред­ко= нуж­но, хо­тя уметь по­лез­но. Ког­да Event соз­дан - он рас­сы­ла­ет­ся всем за­ин= ­те­ре­со­ва­ным ли­цам :), сво­его ро­да рас­сыл­ка по ин­те­ре­сам. Ва­ша прог­рам­ма, ес­= ли она хо­чет по­лу­чать ин­фор­ма­цию о ка­ких-то со­бы­ти­ях - дол­ж­на под­= пи­сать­ся на них. Все до­воль­но прос­то:

    Если вам на­до что­бы ва­ша пр= ог­рам­ма по­лу­ча­ла ин­фор­ма­цию о со­бы­тии на­жа­тия кноп­ки - под­пи­ши­тесь:

    

   &nb= sp;кнопка1.Click +=3D new Even­t­Han­d­ler(на­жа­лиК­ноп­ку);

    Этой строч­кой вы вклю­ча­ете = фун­к­цию "на­жа­лиК­ноп­ку" в спи­сок рас­сыл­ки ин­фор­ма­ции о со­бы­тии "Click" (на­жа­тие) для кноп­ки "кноп­ка1". Пом­нить на= ­до толь­ко об од­ном - лю­бые вла­дель­цы рас­сы­лок лю­ди хит­рые, кон­ку­рен= ­тов не лю­бят и рас­сы­ла­ют толь­ко "сво­им", что в пе­ре­во­де на п= рог­рам­ми­ро­ва­ние зна­чит - фун­к­ция, ко­то­рую вы вно­си­те в спи­сок дол­ж­на быть имен­но= та­ко­го ви­да, ка­ко­го ждут в спис­ке, ина­че не при­мут. Пос­лед­ний мо­мент - Ev= en­t­Han­d­ler - это пос­ред­ник меж­ду спис­ком рас­сыл­ки и по­лу­ча­те­лем, имен­но он = дер­жит имя фун­к­ции, ко­то­рой на­до со­об­щить о со­бы­тии. Even­t­Han­d­ler'ов столь­ко же, сколь­ко и раз­ных event'ов, и он то­же дол­жен быть имен­но т= о­го ти­па, ко­то­рый ждут.

    

    Как чи­тать чу­жой код<= /p>

    Конечно код бы­ва­ет раз­ный­,= и что­бы чи­тать чу­жой код, ко­то­рый на­пи­сан на сла­боз­на­ко­мом язы­ке, без ко= м­мен­та­ри­ев и в неп­ри­выч­ной ма­не­ре нуж­но не толь­ко уме­ние, но и из­ряд­ная до­ля уда­чи и ин­ту­иции. Од­на­ко ес­ли код до­воль­но прос­той и на­пи­сан с к= ом­мен­та­ри­ями, хо­тя бы с ка­ки­ми-то, то про­чи­тать его проб­лем обыч­но не сос­тав­ля­е= т. Боль­шой плюс C# в этом от­но­ше­нии, это воз­мож­ность вбить не­по­нят­ную строч­ку в Go­og­le или MSDN и поч­ти на­вер­ня­ка най­дет­ся при­мер с под= ­роб­ным опи­са­ни­ем, или что-то по­доб­ное. Боль­ше чем про C#, сре­ди .NET язы­ко= в, в се­ти толь­ко про VB на­пи­са­но.

    Итак, раз­бор ко­да. Рас­смот­= рим код внут­ри фун­к­ции, так как все ос­таль­ное обыч­но воп­ро­сов не вы­зы­= ва­ет.

    Код, как при­ви­ло, струк­ту­р= и­ро­ван - или фор­ма­ти­ро­ван та­бу­ля­ци­ей­, или как-то ина­че (нап­ри­мер re­gi= ­on'ами). Очень час­то каж­дый блок име­ет ком­мен­та­рий­, опи­сы­ва­ющий про­из­во­= ди­мую внут­ри опе­ра­цию - нап­ри­мер, "Чте­ние фай­ла", "За­пол­н= е­ние таб­ли­цы", "Под­бор раз­ме­ров". Та­ким об­ра­зом, мы зна­е= т что там про­ис­хо­дит, ос­та­лось по­нять как. Боль­шин­с­т­во, т.е. поч= ­ти все, фун­к­ции и свой­ст­ва стан­дар­т­ных клас­сов об­ла­да­ют наз­ва­ни­я= ми точ­но опи­сы­ва­ющи­ми их дей­ст­вия. Ес­ли вы не зна­ете ан­г­лий­ский - = поп­ро­буй­те сна­ча­ла пе­ре­вес­ти наз­ва­ние фун­к­ции, раз­бив его на от­дель­ные сло= ­ва по боль­шим бук­вам: Get­C­hil­d­F­rom­Po­int =3D Get Child From Po­int =3D= По­лу­чить Ре­бен­ка Из Точ­ки - по­лу­чить до­чер­ний эле­мент уп­рав­ле­ния, на­хо­д= я­щий­ся в точ­ке. Дру­гой ва­ри­ант - пе­ре­во­дить имя клас­са и фун­к­цию: Con­ve= rt.ToS­t­ring =3D Con­vert To String =3D Пе­ре­вес­ти В Стро­ку. Не­ко­то­рые тер­ми­ны, = ко­неч­но, при­дет­ся по­ис­кать в се­ти и пос­ле пе­ре­во­да. А не­ко­то­рые пе­ре­ве= с­ти по­нят­но не по­лу­чит­ся, по­это­му при­дет­ся по­нять, что имен­но фун­к­= ция де­ла­ет. Нап­ри­мер, в та­кой вот строч­ке:

    

   &nb= sp;largerImagesComboBox.DataSource =3D Enum.Get­Na­mes(type­of(Des­k­top­Bac­k­g­ro­un­d­S­t­y­le));

    

    даже ес­ли вы по­ня­тия не име= ­ете что та­кое Enum, мож­но мно­гое по­нять из прос­то­го пе­ре­во­да: объ­ект = Com­bo­Box (вы­па­да­ющее ок­но), его свой­ст­во Ис­точ­ник Дан­ных (Da­ta­So­ur­ce) п= ри­рав­ни­ва­ет­ся че­му-то не­по­нят­но­му. Ло­гич­но пред­по­ло­жить, что ис­точ­ник дан­ных= ука­зы­ва­ет на ка­ко­го-ли­бо ви­да спи­сок зна­че­ний. Фун­к­ция, пред­по­ло­жим не­из= ­вес­т­но­го, клас­са Enum на­зы­ва­ет­ся Get­Na­mes - по­лу­чить име­на, воз­в­ра­ща­ет = она мас­сив строк, ко­то­рый и за­пол­нит вы­па­да­ющее ок­но зна­че­ни­ями.

    

    Подведем ито­ги - ни­че­го тол= ­ком по­лез­но­го я не ска­зал. Все мои со­ве­ты мож­но свес­ти к трем фра­зам:<= /p>

    1. вни­ма­тель­но чи­тай­те ко= д

    2. учи­те ан­г­лий­ский­, кто = не зна­ет

    3. поль­зуй­тесь справ­кой (MS= DN) и Go­og­le'ом.

    

Гла­ва 2. Син­так­сис дек­ла­ра­ций.=

  &nb= sp; 

    Вторая по­пыт­ка на­пи­сать чт= о-то по­лез­ное для сов­сем на­чи­на­ющих в С#. 

    Чуть-чуть о струк­ту­ре ко­да:= вер­х­ним эле­мен­том струк­ту­ры яв­ля­ют­ся прос­т­ран­с­т­ва имен (na­mes­pa­ce), = до­воль­но фик­тив­ная вешь, приз­ван­ная упо­ря­до­чить ту ку­чу клас­сов, спис­ков, = ин­тер­фей­сов и кон­с­тант, ко­то­рые уже су­щес­т­ву­ют и бу­дут соз­да­вать­ся. В na­me= s­pa­ce мо­гут вхо­дить дру­гие na­mes­pa­ce, клас­сы, enum, кон­с­тан­ты и ин­тер­= фей­сы. Класс сос­то­ит из фун­к­ций­, пе­ре­мен­ных и кон­с­тант. По по­во­ду пе­р= е­мен­ных - они мо­гут быть объ­яв­ле­ны на лю­бом уров­не, и су­щес­т­во­вать бу­дут толь­ко в пре­де­лах (и во вре­мя жиз­ни) то­го бло­ка, в ко­то­ром объ­яв­= ле­ны. Нап­ри­мер: пе­ре­мен­ная объ­яв­лен­ная в клас­се дос­туп­на для все­го кл= ас­са, час­то для дру­гих клас­сов, жи­вет все вре­мя, по­ка жив класс (если он ст= а­тич­ный­) или объ­ект клас­са (если не ста­тич­ный­). Дру­гой при­мер: пе­ре­мен­ная = объ­яв­лен­ная в пре­де­лах бло­ка if { }, ко­то­рый на­хо­дит­ся внут­ри бло­ка for { }, = ко­то­рый на­хо­дит­ся внут­ри фун­к­ции клас­са... та­кая пе­ре­мен­ная бу­дет дос­т= уп­на толь­ко в пре­де­лах бло­ка if {}, в ко­то­ром объ­яв­ле­на и бу­дет жить п= о­ка не за­кон­чит­ся вы­пол­не­ние бло­ка, т.е. да­же до кон­ца фун­к­ции не до= ­жи­вет.

    О том, что та­кое ста­ти­чес­к= ий класс, что зна­чит пе­ре­мен­ная объ­яв­ле­на и пр - чи­тай­те ни­же.

    

    Синтаксис дек­ла­ра­ций=

    Декларации - или объ­яв­ле­ния= - это оп­ре­де­ле­ние име­ни и фор­ми­ро­ва­ние ти­па, прик­реп­ля­емо­го к это­му име­ни. Т.е. ес­ли вам нуж­но мес­то для хра­не­ния це­ло­го чис­ла, вы дол= ­ж­ны объ­явить пе­ре­мен­ную, с по­нят­ным вам име­нем, ти­па дан­ных це­лое (in= t). К сло­ву, int =3D in­te­ger =3D це­лое чис­ло. И пом­ни­те, дек­ла­ра­ция н= е соз­да­ет объ­ект и не вы­де­ля­ет па­мять - это толь­ко зак­реп­ле­ние име­ни за ти­= пом. Для соз­да­ния дек­ла­ри­ро­ван­но­го объ­ек­та не­об­хо­ди­мо его ини­ци­а= ли­зи­ро­вать (или оп­ре­де­лить), т.е. ли­бо прис­во­ить на­чаль­ное зна­че­ние, ли­бо з= а­пус­тить кон­с­т­рук­тор.

    Декларация клас­сов, фун­к­ций= и пе­ре­мен­ных на уров­не клас­са тре­бу­ет ука­за­ния мо­ди­фи­ка­то­ра дос­ту­па. Впро­ч= ем, ес­ли не ука­зы­вать бу­дет ис­поль­зо­ван стан­дар­т­ный­, обыч­но pri­va­= te. Мо­ди­фи­ка­то­ры дос­ту­па - это клю­че­вые сло­ва, оп­ре­де­ля­ющие, от­к= у­да мож­но бу­дет по­лу­чить дос­туп к дек­ла­ри­ро­ван­ной пе­ре­мен­ной­/фун­= к­ции и пр. Их все­го нес­коль­ко:

    private - дос­туп воз­м= о­жен толь­ко из­нут­ри клас­са, в ко­то­ром объ­яв­ле­на.

    protected - дос­туп воз= ­мо­жен из это­го клас­са и всех нас­лед­ни­ков.

    internal - дос­туп воз­= мо­жен из всей as­sembly (сбор­ки/биб­ли­оте­ки, фай­ла ко­ро­че). Так­же при­ме­н= я­ет­ся для клас­сов, enum и ин­тер­фей­сов.

    public - дос­туп воз­мо= ­жен от­ку­да угод­но. Так­же при­ме­ня­ет­ся для клас­сов, enum и ин­тер­фей­со= в.

    Есть еще мо­ди­фи­ка­то­ры сос= ­то­яния, оп­ре­де­ля­ющие прин­ци­пи­аль­ное сос­то­яние фун­к­ций­:

    static - фун­к­ция ста­= тич­на, т.е. воз­мож­но ее ис­пол­не­ние без соз­да­ния объ­ек­та клас­са. Нап­ри­м= ер, фун­к­ции клас­са System.Math - ста­тич­ные, пос­коль­ку вы­пол­ня­ют­ся пр= ос­тым вы­зо­вом, без соз­да­ния объ­ек­та клас­са Math. Так­же при­ме­ня­ет­ся для клас­сов, тог­да все вхо­дя­щие в не­го фун­к­ции и пе­ре­мен­ные дол­ж­ны = быть sta­tic.

    abstract - фун­к­ция то= ль­ко дек­ла­ри­ро­ва­на, а оп­ре­де­ле­ние фун­к­ции дол­ж­но быть в клас­сах на= с­лед­ни­ках. Ис­поль­зу­ет­ся для соз­да­ния шаб­ло­на клас­са, все нас­лед­ни­ки ко­то­= ро­го обя­за­тель­но име­ют не­кий на­бор фун­к­ций. Мо­жет су­щес­т­во­вать толь= ­ко в ab­s­t­ract клас­се. Так­же при­ме­ня­ет­ся для клас­сов.

    virtual - фун­к­цию мож= ­но пе­ре­оп­ре­де­лить в клас­се нас­лед­ни­ке.

    override - ис­поль­зу­е= т­ся для ука­за­ния, что опи­сы­ва­емая фун­к­ция пе­ре­оп­ре­де­ля­ет ро­ди­тел= ь­с­кую.

    Модификатор дос­ту­па для фун­= к­ции/пе­ре­мен­ной внут­ри клас­са не мо­жет быть бо­лее дос­туп­ным, не­же­ли мо­ди­фи­ка­тор= дос­ту­па все­го клас­са.

    Переменные, дек­ла­ри­ру­емые = внут­ри фун­к­ций мо­ди­фи­ка­то­ра дос­ту­па не тре­бу­ют.

    

    Декларация пе­ре­мен­ных обя­з= а­тель­но сос­то­ит из ти­па дан­ных и име­ни, ос­таль­ное - по си­ту­ации и же­ла­ни= ю.

    Примеры дек­ла­ра­ций пе­ре­ме= н­ных:

    

   &nb= sp;int i;

   &nb= sp;public do­ub­le myDo­ub­le1, myDo­ub­le2;

   &nb= sp;internal sta­tic System.String con­st_string =3D "кон­с­тан­та";

    

    Декларация фун­к­ций обя­за­те= ль­но сос­то­ит из ти­па воз­в­ра­ща­емых дан­ных, име­ни и спис­ка ар­гу­мен­тов= , ос­таль­ное - по си­ту­ации. Воз­ра­ща­емым ти­пом дан­ных мо­жет быть vo­id - пус­той - т.е. нет воз­в­ра­ща­емых дан­ных. Спи­сок ар­гу­мен­тов то­же мо­жет быть = пус­тым. Фун­к­ции, в от­ли­чии от пе­ре­мен­ных, не мо­гут быть толь­ко дек­ла­ри­р= о­ва­ны - они обя­за­тель­но сра­зу оп­ре­де­ля­ют­ся. Един­с­т­вен­ное ис­к­лю­че­= ние - тип фун­к­ций ab­s­t­ract, они толь­ко дек­ла­ри­ру­ют­ся, а оп­ре­де­ля­= ют­ся уже в клас­сах нас­лед­ни­ках.

    Примеры дек­ла­ра­ций и оп­ре­= де­ле­ния фун­к­ций­:

    

   &nb= sp;private vo­id MyFunc1(int i1, do­ub­le d1) {}

   &nb= sp;internal ab­s­t­ract string Ge­tAs­S­t­ring();

   &nb= sp;internal over­ri­de string Ge­tAs­S­t­ring() {}

   &nb= sp;public sta­tic do­ub­le Get­Sum(do­ub­le d1, do­ub­le d2) { re­turn d1+d2; }<= /o:p>

    

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

    Пример дек­ла­ра­ции клас­са:<= /p>

    

   &nb= sp;internal class MyClass1 : MyPa­ren­t­C­lass1, IIn­ter­fa­ce1, IIn­ter­fa­ce2 {<= /o:p>

   &nb= sp;}

    

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

    

   &nb= sp;public class MyClass : Pa­ren­t­C­lass {

   &nb= sp;public MyClass(int i1) : ba­se() {

   &nb= sp;}

   &nb= sp;public MyClass() : this(0) {

   &nb= sp;}

   &nb= sp;}

    

    И пос­лед­нее - дек­ла­ра­ция = Enum. Enum =3D enu­me­ra­ti­on =3D пе­ре­чис­ле­ние. Спи­сок зна­че­ний. Соз­да­е= т­ся для двух це­лей­: его мож­но ис­поль­зо­вать прог­рам­мис­ту - свя­зать спи­сок= це­ло­чис­лен­ных зна­че­ний с по­нят­ны­ми сло­ва­ми, и поль­зо­вать­ся сло­ва­ми, а не чис­= ла­ми, в ко­то­рых лег­ко за­пу­тать­ся; его мож­но ис­поль­зо­вать поль­зо­ва­те­= лям - име­на зна­че­ний в спис­ке мож­но лег­ко вы­вес­ти на поль­зо­ва­те­ля.<= /p>

    Пример дек­ла­ра­ции Enum:

    

   &nb= sp;public enum Ima­ge­Fi­le­For­mat {

   &nb= sp;JPEG,

   &nb= sp;PNG,

   &nb= sp;TIFF,

   &nb= sp;Bitmap}

   &nb= sp;public enum Sup­por­ted­Lan­gu­ages {

   &nb= sp;Русский=3D1,

   &nb= sp;English=3D10,

   &nb= sp;German=3D11}

    

Гла­ва 3. Син­так­сис дек­ла­ра­ций мас­си­вов, сво= йств, де­ле­га­тов. As­sembly.

  &nb= sp; 

    

    Массивы

    Итак, мас­сив - на­бор объ­ек­= тов од­но­го ти­па, име­ющий чет­кую пос­ле­до­ва­тель­ность этих объ­ек­тов и стро­го з= а­дан­ный раз­мер, т.е. ко­ли­чес­т­во объ­ек­тов в на­бо­ре. Мас­сив мо­жет быть лю­= бо­го ти­па, раз­мер мас­си­ва дол­жен быть боль­ше 0 и мень­ше чем мак­си­маль­н= ое зна­че­ние int (на 32 раз­ряд­ных ма­ши­нах - 2^32=3D4294967296).

    Декларация мас­си­ва по­доб­на= дек­ла­ра­ции пе­ре­мен­ной­, с од­ним толь­ко из­ме­не­ни­ем - пос­ле ти­па дек­ла­ри­ру= ­емо­го мас­си­ва до­бав­ля­ют­ся квад­рат­ные скоб­ки - [].

    Пример:

    

   &nb= sp;int[] in­tAr­ray;

    В строч­ке вы­ше по­ка­за­на д= ек­ла­ра­ция од­но­мер­но­го мас­си­ва ти­па int. Мас­сив мо­жет быть мно­го­мер­ным. Пр= и­чем в .NET мно­го­мер­ность мо­жет быть раз­ной.

    Вариант 1 - мно­го­мер­ный мас= ­сив ста­ро­го об­раз­ца, так на­зы­ва­емый мас­сив-мас­си­вов:

    

   &nb= sp;int[][] int2dAr­ray;

    Система прос­тая - каж­дая ско= б­ка соз­да­ет свой мас­сив. Т.е. мож­но пред­с­та­вить это в та­ком ви­де: (int[])[] ar­ray­Na­me - мас­сив ти­па int[], ко­то­рый то­же яв­ля­ет­ся м= ас­си­вом. По­доб­ных вло­же­ний мо­жет быть нес­коль­ко.<= o:p>

    Вариант 2 - мно­го­мер­ный мас= ­сив .NET:

    

   &nb= sp;int[,] int2dAr­ray;

    

    Внутри скоб­ки ста­вит­ся нуж­= ное ко­ли­чес­т­во за­пя­тых, каж­дая за­пя­тая соз­да­ет плюс од­но из­ме­ре­ние мас­си­ва. Т= .е. в при­ве­ден­ной вы­ше строч­ке дек­ла­ри­ру­ет­ся таб­ли­ца (дву­мер­ный м= ас­сив) для це­ло­чис­лен­ных дан­ных.

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

    

    Свойства

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

    Задаются очень прос­то:

    

   &nb= sp;private flo­at _si­ze; //пе­ре­мен­ная

   &nb= sp;public pro­perty flo­at Si­ze { //свой­ст­во - пос­ред­ник пе­ре­мен­ной _si­ze

   &nb= sp;get {

   &nb= sp;return _si­ze;

   &nb= sp;}

   &nb= sp;set {

   &nb= sp;//проверить на пра­виль­ность

   &nb= sp;_size =3D va­lue;

   &nb= sp;OnSizeChanged();

   &nb= sp;}

   &nb= sp;}

    Сначала, как обыч­но, мо­ди­фи= ­ка­тор дос­ту­па. Клю­че­вое сло­во pro­perty ука­зы­ва­ет, что дек­ла­ри­ру­ет­ся свой­ст­во. Да­лее ука­зы­ва­ет­ся тип дан­ных, в при­ме­ре - оди­нар­ное д= роб­ное. За­тем имя свой­ст­ва и от­к­ры­ва­ет­ся фи­гур­ная скоб­ка, на­чи­на­ющая = блок опи­са­ния. Лю­бое свой­ст­во дол­ж­но иметь блок get - по­лу­чить. Ес­ли с= вой­ст­во толь­ко-для-чте­ния, оно не име­ет бло­ка set - ус­та­но­вить. В каж­дом бл= о­ке про­пи­сы­ва­ет­ся, что на­до сде­лать. Пом­ни­те, что свой­ст­во не хра­нит дан­ные, т.к. не яв­ля­ет­ся пе­ре­мен­ной - оно лишь пос­ред­ник, по­это­м= у в бло­ке get ука­за­но - вер­нуть зна­че­ние пе­ре­мен­ной _si­ze, в ко­то­рой зна­че­ние хра­нит­ся ре­аль­но. В бло­ке set на­пи­са­но, что де­лать при = ус­та­нов­ке но­во­го зна­че­ния - сна­ча­ла про­ве­рить на пра­виль­ность... ну, нап­ри= ­мер, что­бы вво­ди­ли от 0 до 1, ког­да нуж­но и т.п. Пос­ле про­вер­ки - ус­та­= но­вить зна­че­ние; клю­че­вое сло­во va­lue, в дан­ном слу­чае, ука­зы­ва­ет на вх= о­дя­щее зна­че­ние. Ну и под за­на­вес - вы­пол­нить со­бы­тие "раз­мер из­ме­= нил­ся". За­вер­ша­ет дек­ла­ра­цию зак­ры­ва­юща­яся фи­гур­ная скоб­ка.

    Свойства поз­во­ля­ют соз­да­в= ать все пе­ре­мен­ные как pri­va­te, а нуж­ные для от­к­ры­то­го дос­ту­па вы­в= о­дить че­рез свой­ст­ва, имея та­ким об­ра­зом га­ран­тию, что лю­бое внеш­нее из= ­ме­не­ние зна­че­ния бу­дет про­ве­ре­но на пра­виль­ность.

    

    Делегаты

    Делегаты - ес­ли прос­то, то э= то ука­за­те­ли на фун­к­ции. Ес­ли вам на­до пе­ре­дать фун­к­цию как ар­гу­мент, то у вас= два пу­ти - ли­бо че­рез стро­ко­вое наз­ва­ние фун­к­ции ис­кать ее в биб­ли­о= те­ке (assembly) и вы­зы­вать как внеш­нюю фун­к­цию, ли­бо ис­поль­зо­вать де­ле= ­гат. Де­ле­гат поз­во­ля­ет оп­ре­де­лить под­пись фун­к­ции - т.е. воз­в­ра­ща­= емый тип, ти­пы и ко­ли­чес­т­во ар­гу­мен­тов.

    Делегат - это объ­ект уров­ня = клас­са, соб­с­т­вен­но это и есть класс, толь­ко очень осо­бый. Од­на­ко дек­ла­ра­= ция его очень по­хо­жа на дек­ла­ра­цию аб­с­т­рак­т­ной фун­к­ции:

    

   &nb= sp;public de­le­ga­te do­ub­le MyDe­le­ga­te(do­ub­le d1, do­ub­le d2);

    Не за­бы­вай­те, что де­ле­гат= - это класс, и для то­го, что­бы его мож­но бы­ло ис­поль­зо­вать, не­об­хо­ди­мо= соз­дать объ­ект де­ле­га­та, ко­то­рый при­пи­сы­ва­ет­ся к кон­к­рет­ной фун­к­ции= и даль­ше пе­ре­да­ет­ся, ес­ли ну­жен. У де­ле­га­та есть ме­тод In­vo­ke, к= о­то­рый соб­с­т­вен­но и за­пус­ка­ет фун­к­цию, на ко­то­рую де­ле­гат ука­зы­ва­е= т.

    Пример ис­поль­зо­ва­ния де­ле= ­га­та внут­ри фун­к­ции:

    

   &nb= sp;MyDelegate md =3D new MyDe­le­ga­te(MyFun­c­ti­on);

   &nb= sp;md.Invoke(d1, d2);

    В при­ме­ре пред­по­ла­га­ет­с= я, что d1 и d2 - пе­ре­мен­ные ти­па do­ub­le, a фун­к­ция MyFun­c­ti­on име­ет по= д­пись: do­ub­le MyFun­c­ti­on(do­ub­le d1, do­ub­le d2).

    

    Assembly

    Кратко: As­sembly =3D сбор­ка,= файл, яв­ля­ющий­ся кон­тей­не­ром для na­mes­pa­ce/клас­сов/кон­с­тант/де­ле­га­= тов/интер­фей­сов и ре­сур­сов (кар­ти­нок/тек­с­та/ико­нок и пр.). As­sembly мо­жет быть за­= пус­к­ным (exe) или не за­пус­к­ным (dll). От­ли­чие толь­ко в том, что в за­пус­к­ном фай­ле про­пи­сан ме­тод Win­Ma­in (или ma­in для кон­соль­ных при­ло­же­ни= й­). Нес­мот­ря на рас­ши­ре­ния фай­ла, ни­че­го об­ще­го (кро­ме поль­зо­ва­те= ль­с­ко­го при­ме­не­ния) со ста­ры­ми (до .NET) фай­ла­ми не име­ет. Ос­нов­ные два о= т­ли­чия от win32 стан­дар­тов - as­sembly име­ет соб­с­т­вен­ное опи­са­ние внут­ри= се­бя. Опи­са­ние вклю­ча­ет: вер­сию, ав­то­ра, па­ра­мет­ры бе­зо­пас­нос­ти не­= об­хо­ди­мые для вы­пол­не­ния и мно­гое дру­гое, в час­т­нос­ти - опи­са­ние под­пи­сей всех фун­к­ций­, пе­ре­мен­ных и т.д. Т.е. не на­до знать за­ра­нее как вы­= зы­вать ту или иную фун­к­цию из биб­ли­оте­ки - это мож­но уз­нать на ле­ту. Вто­р= ое от­ли­чие - as­sembly хра­нит код не в ви­де ас­сем­б­ле­ра, а в MSIL - Mic= ­ro­soft In­ter­me­di­ate Lan­gu­age - сво­е­об­раз­ный ме­та-язык, ко­то­рый ком­пи= ­ли­ру­ет­ся на ле­ту и по зап­ро­су (just-in-ti­me com­pi­ling and de­bug­ging) в со­от= ­вет­с­т­вии с тре­бо­ва­ни­ями сис­те­мы на ко­то­рой ком­пи­ли­ру­ет­ся (раз­ряд­ность= про­цес­со­ра и т.д.). Плю­сы та­ко­го под­хо­да - кросс-плат­фор­мен­ность и кросс-сис­т= ем­ность, а так­же бе­зо­пас­ность вы­пол­не­ния. Ми­ну­сы - код всег­да от­к­рыт и д= ос­ту­пен лю­бо­му для проч­те­ния, а так­же ухо­дит вре­мя и ре­сур­сы на ком­пи­ля­= цию. Впро­чем, кни­ги и му­зы­ку то­же мо­жет лю­бой про­чи­тать/услы­шать... и = по­че­му-то ав­то­ров это не нап­ря­га­ет, а пла­ги­ато­ров не так уж мно­го...

    Assembly мо­жет быть од­но­мо­= дуль­ным и мно­го­мо­дуль­ным. Мо­дуль - часть as­sembly, как пра­ви­ло, до сбор­ки = пред­с­тав­ля­ющая со­бой от­дель­ный файл. Т.е. нес­коль­ко биб­ли­отек клас­сов мож­но объ­е= ди­нить в од­ну as­sembly, и до­ба­вить еще кар­тин­ки до ку­чи. Каж­дая кар­тин­ка= и каж­дая биб­ли­оте­ка бу­дут от­дель­ным мо­ду­лем внут­ри еди­ной as­sembl= y. Плю­сов в та­ком под­хо­де ма­ло­ва­то, по­это­му ред­ко ис­поль­зу­ет­ся. = MS Vi­su­al Stu­dio да­же не име­ет воз­мож­нос­ти соз­да­вать муль­ти­мо­дуль= ­ные as­sembly в ин­тер­фей­сном ре­жи­ме.

    Лично я от­но­шусь к as­sembly= по ста­ро­му - как к exe и dll win32 стан­дар­та. Един­с­т­вен­ное, о чем при­= хо­дит­ся пом­нить - as­sembly дол­ж­ны иметь иден­ти­фи­ка­то­ры, ес­ли ими со­би­ра= ­етесь поль­зо­вать­ся не вы один. Т.е. вер­сия, ав­тор и раз­ре­ше­ния бе­зо­пас­= нос­ти дол­ж­ны быть про­пи­са­ны. Да и под­пи­сать as­sembly клю­чом strong na­me= то­же нуж­но. Ключ strong na­me - уни­каль­ный иден­ти­фи­ка­тор as­sembly, ко­то= ­рый ис­поль­зу­ет­ся для рас­поз­на­ва­ния раз­ных as­sembly с од­ним име­нем н= а од­ной ма­ши­не. Для под­пи­сы­ва­ния есть кон­соль­ная ути­ли­та sn.exe, а в VS20= 05 есть и ин­тер­фей­сные оп­ции в свой­ст­вах про­ек­та для этой це­ли.

    Если кто-то хо­чет ра­зоб­рать= ­ся под­роб­нее - есть неп­ло­хая статья (ку­сок кни­ги) на co­dep­ro­j­ect: лежит здесь - http://www.co­dep­ro­j­ect.com/bo­oks/1893115593_6.asp.

    

Глава 4. Основные операторы и конструкты.

  &nb= sp; 

    Рассмотрев син­так­сис дек­ла­= ра­ций­, пе­ре­хо­дим к ос­нов­ным кон­с­т­рук­там или ко­ман­дам язы­ка. Как из­вес= ­т­но, код внут­ри фун­к­ции вы­пол­ня­ет­ся пос­т­роч­но, ес­ли не ис­поль­зу­ют­= ся ка­кие-ли­бо ко­ман­ды. Дру­ги­ми сло­ва­ми - ко­ман­ды язы­ка приз­ва­ны о= бес­пе­чить не­ли­ней­ность вы­пол­не­ния ко­да. Са­мая из­вес­т­ная из не-ли­ней­ных к= о­манд - go­to - есть в язы­ке C#, од­на­ко я нас­то­ятель­но не ре­ко­мен­дую ей = поль­зо­вать­ся. Ос­таль­ных ко­манд впол­не дос­та­точ­но, что­бы обес­пе­чить лю­бую не­ли= ­ней­ность, а при­вы­кать к go­to - это как пра­ви­ло оз­на­ча­ет при­выч­ку к пло­хой струк­ту­ри­за­ции ко­да.

    Но сна­ча­ла, что­бы бы­ло про= ­ще по­ни­мать ко­ман­ды, рас­смот­рим не­ко­то­рые опе­ра­то­ры язы­ка.

    

    Операторы

    Операторы срав­не­ния:

    ‹ - стро­го мень­ше. Оп­ре­де­= лен для лю­бых чис­ло­вых ти­пов (int, do­ub­le, short, byte, de­ci­mal, flo­at= , bo­ol)

    › - стро­го боль­ше. Ана­ло­ги= ч­но пре­ды­ду­ще­му.

    ‹=3D - мень­ше или рав­но. Ана= ­ло­гич­но пре­ды­ду­ще­му.

    ›=3D - боль­ше или рав­но. Ана= ­ло­гич­но пре­ды­ду­ще­му.)

    =3D=3D - рав­но (экви­ва­лен­т= ­но). Оп­ре­де­лен для боль­шин­с­т­ва ти­пов и клас­сов. Для мно­гих клас­сов оз­на­ча­ет име= н­но иден­тич­ность объ­ек­тов клас­са, а не ра­вен­с­т­во их внут­рен­них зна­ч= е­ний.

    !=3D - не рав­но. Ана­ло­гич­н= о пре­ды­ду­ще­му.

    Обратите вни­ма­ние: про­вер­к= а на ра­вен­с­т­во обоз­на­ча­ет­ся дву­мя зна­ка­ми рав­но! Од­ним зна­ком обоз= ­на­ча­ет­ся опе­ра­тор прис­во­ения зна­че­ния.

    

    Логические опе­ра­то­ры:

    || - ло­ги­чес­кое ИЛИ.

    && - логическое И= .

    ! - ло­ги­чес­кое НЕ.

    

    Арифметические опе­ра­то­ры:

    помимо стан­дар­т­ных +, -, *,= / есть их мо­ди­фи­ка­ции со зна­ком рав­но: +=3D, -=3D, *=3D, /=3D. Оз­на­ча= ­ют "вы­пол­нить опе­ра­тор и при­рав­нять", т.е.

    

   &nb= sp;i +=3D 10;

    означает при­ба­вить 10 к зна­= че­нию пе­ре­мен­ной i, и за­пи­сать ре­зуль­тат в нее же. Это ана­ло­гич­но за­пи= ­си:

    

   &nb= sp;i =3D i + 10;

    есть еще два опе­ра­то­ра:

    ++ - при­ба­вить еди­ни­цу к п= е­ре­мен­ной и за­пи­сать ре­зуль­тат в нее. Ин­к­ре­мен­т­ный опе­ра­тор.

    -- - от­нять еди­ни­цу от зна­= че­ния пе­ре­мен­ной и за­пи­сать ре­зуль­тат в нее. Дек­ре­мен­т­ный опе­ра­тор.<= /p>

    

    

    Основные кон­с­т­рук­ты мож­но= раз­де­лить на цик­ло­вые - поз­во­ля­ющие за­пус­кать ку­сок ко­да в цик­ле, и ус­лов­= ные - поз­во­ля­ющие вы­пол­нять нуж­ный ку­сок ко­да, вы­би­ра­емый по ус­ло­в= ию. Я опи­шу ос­нов­ные кон­с­т­рук­ты, ес­ли ка­кой за­бу­ду, а вам он ин­те­р= е­сен - пи­ши­те.

    

    Циклы

    Самый рас­п­рос­т­ра­нен­ный ц= икл - for. Смыс­ло­вое пред­наз­на­че­ние - вы­пол­нять пос­ле­до­ва­тель­ность д= ей­ст­вий за­дан­ное ко­ли­чес­т­во раз. За­да­ет­ся сле­ду­ющим об­ра­зом:

    

   &nb= sp;for (i =3D 0; i ‹ 10; i++) { /*Ваш код здесь*/ }

    После клю­че­во­го сло­ва for = сле­ду­ет круг­лая скоб­ка, внут­ри кто­рой обя­за­тель­но при­сут­с­т­ву­ют три вы­р= а­же­ния, раз­де­лен­ные точ­кой с за­пя­той. Пер­вое вы­ра­же­ние, в на­шем слу­чае "i=3D0" - опе­ра­ция, ко­то­рую на­до вы­пол­нить пе­ред на­ча­ло= м цик­ла. За­час­тую в этом мес­те пи­шут дек­ла­ра­цию пе­ре­мен­ной­, ко­то­рая су­= щес­т­ву­ет толь­ко для цик­ла, тог­да вы­ра­же­ние выг­ля­дит, нап­ри­мер так: "i= nt i =3D 0". С тем же ус­пе­хом по­ле мо­жет быть пус­тым. Вто­рое вы­ра­же= ­ние ус­та­нав­ли­ва­ет ус­ло­вие, при ко­то­ром цикл дол­жен про­дол­жать­ся...= его мож­но рас­смат­ри­вать как "Про­дол­жать цикл по­ка вы­ра­же­ние 2 ис= ­тин­но". Мо­жет быть пус­тым, но тог­да вам са­мим при­дет­ся внут­ри цик­ла про­ве­= рять ус­ло­вие вы­хо­да и са­мим же вы­хо­дить из цик­ла. Третье вы­ра­же­ние за= ­да­ет опе­ра­цию, ко­то­рую на­до вы­пол­нить по за­вер­ше­нии каж­до­го кру­га ц= ик­ла. То­же мо­жет быть пус­тым.

    Итак в строч­ке при­ме­ра по­к= а­зан цикл, об­ну­ля­ющий пе­ре­мен­ную i вна­ча­ле, ис­пол­ня­ющий­ся по­ка пе­р= е­мен­ная i мень­ше 10, пос­ле каж­до­го кру­га уве­ли­чи­ва­ющий i на 1, что да­ет 1= 0 вы­пол­не­ний цик­ла, при нор­маль­ных ус­ло­ви­ях.

    Для до­пол­ни­тель­но­го уп­ра= в­ле­ния вы­пол­не­ни­ем цик­ла пре­дус­мот­ре­ны еще два клю­че­вых сло­ва: con­ti­= nue - за­вер­ша­ет те­ку­щий круг вы­пол­не­ния, bre­ak - за­вер­ша­ет цикл.

    

   &nb= sp;for (int i =3D 0; i ‹ 10; i ++) { // цикл на 10 кру­гов

   &nb= sp;if (so­me_array[i] =3D=3D 0) { con­ti­nue;} // ес­ли i-тая ячей­ка мас­си­ва р= ав­на 0 - пой­ти на след круг

   &nb= sp;int res =3D Do­So­met­hing(so­me_array[i]); // что-то сде­лать с ячей­кой мас­с= и­ва и вер­нуть зна­че­ние

   &nb= sp;if (res =3D=3D -1) { bre­ak;} // ес­ли вер­ну­лось -1 - прер­вать цикл

   &nb= sp;}

    

    Цикл whi­le (быв­ший do...whi­= le). Смыс­ло­вое пред­наз­на­че­ние - вы­пол­нять пос­ле­до­ва­тель­ность дей­ст= ­вий­, по­ка что-то не слу­чит­ся/изме­нит­ся.

    задается так:

    

   &nb= sp;while (flag =3D=3D true) { /*Ваш код здесь*/ }

    После клю­че­во­го сло­ва whi­= le сле­ду­ет круг­лая скоб­ка, в ко­то­рой за­да­ет­ся ло­ги­чес­кое вы­ра­же­ние, про­в= е­ря­емое на ис­тин­ность каж­дый круг цик­ла. Цикл вы­пол­ня­ет­ся по­ка вы­ра­же­ни= е ис­тин­но, или до клю­че­во­го сло­ва bre­ak, внут­ри цик­ла. Сло­во con­ti­nue при­ме= ­ни­мо к это­му цик­лу так же, как и к пре­ды­ду­ще­му.

    

    Цикл fo­re­ach. Смыс­ло­вое пр= ед­наз­на­че­ние - вы­пол­нить ку­сок ко­да для каж­до­го чле­на мас­си­ва/спис­ка/кол­лек­ц= ии. В .NET по­яви­лись клас­сы обес­пе­чи­ва­ющие не­ну­ме­ро­ван­ные, ди­на­ми= ­чес­кие мас­си­вы. В ос­нов­ном для них и был сде­лан этот опе­ра­тор.

    задается так:

    

   &nb= sp;foreach (Class obj­ec­t­Na­me in col­lec­ti­on­Na­me) { /* Ваш код здесь */ }<= /o:p>

    После клю­че­во­го сло­ва fo­r= e­ach сле­ду­ет круг­лая скоб­ка в ко­то­рой за­да­ет­ся класс оди­ноч­но­го объ­= ек­та, над ко­то­рым вы­пол­ня­ет­ся опе­ра­ция в ос­нов­ном бло­ке, имя объ­ек­та= (пе­ре­мен­ной­) для об­ра­ще­ния в ос­нов­ном бло­ке, за­тем клю­че­вое сло­во in и имя пе­= ре­мен­ной­, ука­зы­ва­ющее на мас­сив/спи­сок/кол­лек­цию объ­ек­тов то­го клас­са, ко­= то­рый ука­за­ли в на­ча­ле ус­ло­вия. Ос­нов­ной код, как обыч­но, пи­шет­ся в фи= ­гур­ных скоб­ках.

    Ну, нап­ри­мер, час­то встре­ч= а­ет­ся - цикл по всем фай­лам ка­ко­го-ли­бо ти­па в ка­та­ло­ге:

    

   &nb= sp;DirectoryInfo di =3D new Di­rec­tor­yIn­fo("C:\\temp"); // соз­дать объ­ект ин­= фор­ма­ции о ка­та­ло­ге С:\temp

   &nb= sp;foreach (Fi­le­In­fo fi in di.Get­Fi­les("*.txt")) { // для каж­до­го объ= ­ек­та ти­па Fi­le­In­fo в кол­лек­ции, воз­в­ра­ща­емой фун­к­ци­ей Get­Fi­les

   &nb= sp;// что-то сде­лать

   &nb= sp;}

    

    Условия

    Условие if...else. Смыс­ло­вое= пред­наз­на­че­ние - вы­пол­нить блок ко­да, толь­ко ес­ли ус­ло­вие ис­тин­но... с ва­ри­ан­т= ом - ес­ли лож­но - вы­пол­нить дру­гой ку­сок ко­да.

    задается так:

    

   &nb= sp;if (flag =3D=3D true) { /* Ваш код здесь */ }

   &nb= sp;else if (flag2 =3D=3D true) { /* Ваш код здесь */ }

   &nb= sp;else { /* Ваш код здесь */ }

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

    Пример:

    

   &nb= sp;bool flag1 =3D true, flag2 =3D fal­se;

   &nb= sp;if (flag1 && flag2) {Do­So­met­hing(); }

   &nb= sp;else if (flag1) { Do­So­met­hing2(); }

   &nb= sp;else { Do­So­met­hing3(); }

    В этом при­мер бу­дет вы­пол­н= е­на толь­ко фун­к­ция Do­So­met­hing2(). В пер­вом ус­ло­вии мы про­ве­ря­ем оба фла­га на ис­тин­ность, ес­ли хо­тя бы один не ра­вен ис­тин­но, пе­ре­хо­д= им на вто­рой блок. Вто­рой блок то­же с ус­ло­ви­ем - про­ве­ря­ем пер­вый фл= аг на ис­тин­ность - мы точ­но зна­ем, что хо­тя бы один флаг ло­жен, ес­ли пе= р­вый ис­тин­нен - вто­рой ло­жен. Тре­тий блок - ес­ли пер­вый не ис­тин­нен, то= ли­бо вто­рой ис­тин­нен, ли­бо оба лож­ны, но воз­мож­но нам это уже не важ­но..= .

    

    Пример 2:

    

   &nb= sp;int i =3D 1;

   &nb= sp;if (i =3D=3D 0) { Do­So­met­hing1(); }

   &nb= sp;else if (i =3D=3D 1) { Do­So­met­hing2(); }

   &nb= sp;else if (i =3D=3D 2) {Do­So­met­hing3(); }

   &nb= sp;else { Do­El­se(); }

    В этом, край­не нег­ра­мот­ном= при­ме­ре, то­же бу­дет вы­пол­не­на толь­ко фун­к­ция Do­So­met­hing2(). А гра­мот­но= та­кую си­ту­ацию рас­пи­сы­вать че­рез опе­ра­тор switch;

    

    Условие swit­ch...ca­se. Смыс­= ло­вое пред­наз­на­че­ние - вы­пол­нять кус­ки ко­да по зна­че­нию пе­ре­мен­ной. = Сво­е­об­раз­ная раз­вил­ка (или пе­рек­лю­ча­тель). С до­пол­ни­тель­ным воз­мож­нос­тя­ми.=

    задется так:

    

   &nb= sp;switch (var) {

   &nb= sp;case ‹val1›:

   &nb= sp;DoSomething();

   &nb= sp;case ‹val2›:

   &nb= sp;DoSomething2();

   &nb= sp;break;

   &nb= sp;default:

   &nb= sp;DefaultAction();

   &nb= sp;break;

   &nb= sp;}

    После клю­че­во­го сло­ва swit= ch сле­ду­ет имя пе­ре­мен­ной­, по чьему зна­че­нию нуж­но раз­вет­вить код. В фи­гур­н= ых скоб­ках за­да­ют­ся ва­ри­ан­ты зна­че­ний­, ко­то­рые дол­ж­ны об­ра­ба­т= ы­вать­ся. Ва­ри­ант зна­че­ния за­да­ет­ся как клю­че­вое сло­во ca­se, зна­че­ние пе= ­ре­мен­ной­, дво­ето­чие. Ес­ли фун­к­ци­ональ­ный блок за­кан­чи­ва­ет­ся сло­вом bre­a= k - прог­рам­ма вы­хо­дит из бло­ка switch пос­ле не­го, ес­ли не за­кан­чи­ва­= ет­ся - прог­рам­ма пе­ре­хо­дит на вы­пол­не­ние сле­ду­юще­го по по­ряд­ку бло­= ка ca­se. клю­че­вое сло­во de­fa­ult на мес­те ca­se оз­на­ча­ет "все ва= ­ри­ан­ты".

    Пример:

    

   &nb= sp;int i =3D 1;

   &nb= sp;switch (i) {

   &nb= sp;case 2:

   &nb= sp;DoSomething2();

   &nb= sp;break;

   &nb= sp;case 1:

   &nb= sp;DoSomething1();

   &nb= sp;case 3:

   &nb= sp;DoSomething3();

   &nb= sp;break;

   &nb= sp;default:

   &nb= sp;DoDefault();

   &nb= sp;break;

   &nb= sp;}

    В этом при­ме­ре бу­дут вы­пол= ­не­ны фун­к­ции Do­So­met­hing1() и Do­So­met­hing3().

    Переменная мо­жет быть не чис­= ло­вой­:

    

   &nb= sp;char c =3D '!';

   &nb= sp;switch (c) {

   &nb= sp;case '?':

   &nb= sp;DoQuestion();

   &nb= sp;break;

   &nb= sp;case '!':

   &nb= sp;DoExclamation();

   &nb= sp;break;

   &nb= sp;}

    В этом при­ме­ре бу­дет вы­пол= ­не­на толь­ко фун­к­ция Do­Ex­c­la­ma­ti­on(), а ес­ли пе­ре­мен­ная с бу­дет рав= ­на не воп­ро­си­тель­но­му зна­ку и не вос­к­ли­ца­тель­но­му - ни­че­го сде­л= а­но не бу­дет.

    

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

    

   &nb= sp;if (flag =3D=3D true) re­turn;

    

Гла­ва 5. Ини­ци­али­за­ция пе­ре­мен­ных. Кол­лек­= ции. Ти­пы дан­ных.

  &nb= sp; 

    

    Инициализация.

    Я уже упо­ми­нал это сло­во, в= та­ком при­мер­но кон­тек­с­те - Пос­ле дек­ла­ра­ции пе­ре­мен­ной­, пе­ред ее ис= ­поль­зо­ва­ни­ем, не­об­хо­ди­мо эту пе­ре­мен­ную ин­ци­али­зи­ро­вать. Поп­ро­бу­ем ра­зоб­= рать­ся точ­нее: ини­ци­али­за­ция - про­цесс вы­де­ле­ния па­мя­ти под пе­ре­мен­н= ую и за­пол­не­ние этой па­мя­ти зна­че­ни­ями по умол­ча­нию. Зна­че­ния по умо= л­ча­нию - это, поч­ти всег­да, раз­ные фор­мы ну­ля: для int - 0, для do­ub­le - 0.= 0, для string - "", для bo­ol - fal­se и т. д.

    Та часть, ко­то­рая ка­са­ет­с= я вы­де­ле­ния па­мя­ти, это те­перь де­ло ком­пи­ля­то­ра и к прог­рам­мис­ту поч­ти не и= ме­ет ни­ка­ко­го от­но­ше­ния. С точ­ки зре­ния прог­рам­мис­та, ини­ци­али­за­ц= ия - пер­вое прис­во­ение зна­че­ния пе­ре­мен­ной.

    Есть та­кая тон­кость - пе­ре­= мен­ные, объ­яв­лен­ные на уров­не клас­са ини­ци­али­зи­ру­ют­ся ав­то­ма­ти­чес­ки= при соз­да­нии объ­ек­та клас­са. Но это от­но­сит­ся толь­ко к ос­нов­ным ти­п= ам дан­ных (int, do­ub­le, bo­ol, byte и т. д.) кро­ме string. Все пе­ре­мен­н= ые клас­сов по­лу­ча­ют при ав­то­ма­ти­чес­кой ини­ци­али­за­ции зна­че­ние n= ull и string то­же.

    Комментарий для спе­ци­алис= ­тов: ес­ли быть точ­ным, то null - это зна­че­ние по умол­ча­нию для всех пе­ре­= мен­ных клас­сов Mar­s­hal­B­y­Re­fe­ren­ce - об­ра­ба­ты­ва­емых че­рез ука­за­тел= ь. Ос­нов­ные ти­пы дан­ных яв­ля­ют­ся клас­са­ми Mar­s­hal­B­y­Va­lue - об­р= а­ба­ты­ва­емых че­рез зна­че­ние, по­это­му им прис­ва­ива­ют­ся нор­маль­ные зна­че­ния. Класс string по преж­не­му яв­ля­ет­ся мас­си­вом, хоть и очень хит­рым, и = об­ра­ба­ты­ва­ет­ся со­от­вет­с­т­вен­но.

    Инициализация бы­ва­ет двух ти= ­пов:

    для прос­тых ти­пов дан­ных эт= о прос­тое прис­во­ение зна­че­ния:

    

   &nb= sp;int i =3D 0;

   &nb= sp;double d =3D 0.2;

   &nb= sp;string s =3D "test string"

    Все это ва­ри­ан­ты дек­ла­ра­= ции с од­нов­ре­мен­ной ини­ци­али­за­ци­ей для прос­тых ти­пов дан­ных.

    Второй ва­ри­ант - ини­ци­али­= за­ция объ­ек­та клас­са:

    

   &nb= sp;DateTime dt =3D new Da­te­Ti­me();

   &nb= sp;MyClass mc =3D new MyClass();

   &nb= sp;int[] in­tAr­ray =3D new int[20];

    Обратите вни­ма­ние - мас­си­в= ы рас­смат­ри­ва­ют­ся как клас­сы.

    Для спе­ци­алис­тов: мас­си= в, как из­вес­т­но, об­ра­ба­ты­ва­ет­ся по ука­за­те­лю. А прос­тые ти­пы дан­ных= то­же мо­гут быть ини­ци­али­зи­ро­ва­ны как клас­сы: int i =3D new int(); впол­н= е пра­виль­ная за­пись. Бо­лее то­го, за­пись "int i =3D 2;" для ком­пи­ля­то­ра= рав­на "int i =3D new int(); i =3D 2;".

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

    

   &nb= sp;public par­ti­al class Form1 : Form

   &nb= sp;{

   &nb= sp;public Da­te­Ti­me dt =3D new Da­te­Ti­me();

    И пос­лед­нее: ошиб­ка при ком= ­пи­ля­ции "Use of unas­sig­ned lo­cal va­ri­ab­le ..." зна­чит, что вы за­б= ы­ли ини­ци­али­зи­ро­вать пе­ре­мен­ную, дек­ла­ри­ро­ван­ную внут­ри фун­к­ции= , а ошиб­ка "Null re­fe­ren­ce ex­cep­ti­on ..." при ра­бо­те прог­ра= м­мы оз­на­ча­ет, что вы за­бы­ли соз­дать объ­ект клас­са для пе­ре­мен­ной­, д= ек­ла­ри­ро­ван­ной на уров­не клас­са.

    

    Коллекции/списки и мас­си­в= ы

    Что та­кое мас­си­вы мы уже ра= с­смот­ре­ли, те­перь рас­смот­рим та­кую вещь как кол­лек­цию/спи­сок и срав­ним.

    Список - это лю­бой класс, ко­= то­рый под­дер­жи­ва­ет ин­тер­фей­с IList, поз­во­ля­ющий соз­да­вать ди­на­ми­че= с­кие мас­си­вы. Ди­на­ми­чес­кий мас­сив - на­бор объ­ек­тов од­но­го клас­са, с= из­ме­ня­ющим­ся ко­ли­чес­т­вом и по­ряд­ком объ­ек­тов по хо­ду вы­пол­не­ния прог­рам­мы.= Ба­зо­вый ин­тер­фей­с IList со­дер­жит фун­к­ции для до­бав­ле­ния но­во­го объ­ек­т= а к спис­ку, уби­ра­ния объ­ек­та из спис­ка, встав­ки объ­ек­та в спи­сок, по­= ис­ка объ­ек­та и очис­т­ки спис­ка.

    Коллекция - лю­бой класс по­ро= ж­ден­ный от клас­са Col­lec­ti­on­Ba­se, обес­пе­чи­ва­юще­го фун­к­ции под­дер­ж­ки спис­ка. Класс Col­lec­ti­on­Ba­se под­дер­жи­ва­ет ин­тер­фей­с IList.

    Есть мо­ди­фи­ка­ции этих двух= клас­сов для соз­да­ния кол­лек­ций толь­ко-для-чте­ния, раз­но­ти­по­вых кол­лек­ци= й­, пар­ных мас­си­вов (хе­шей­) и пр.

    Преимущества спис­ков - ди­на­= мич­ность вы­де­ля­емой па­мя­ти.

    Преимущества мас­си­вов - ско­= рость.

    Как поль­зо­вать­ся - ес­ли оч= ень не лень, или очень на­до - мо­же­те всег­да соз­дать свой класс. Та­кой ва­ри­= ант мы рас­смот­рим поз­же, ког­да бу­дем го­во­рить о нас­ле­до­ва­нии. Про­ще= все­го поль­зо­вать­ся клас­сом Ar­ray­List:

    

   &nb= sp;ArrayList al =3D new Ar­ray­List();

   &nb= sp;int i =3D 2;

   &nb= sp;double d =3D 4.5;

   &nb= sp;string s =3D "test";

   &nb= sp;MyClass mc =3D new MyClass();

   &nb= sp;al.Add(i);

   &nb= sp;al.Add(d);

   &nb= sp;al.Add(s);

   &nb= sp;al.Add(mc);

    Таким пу­тем мы до­ба­ви­ли са= ­мые раз­ные эле­мен­ты в спи­сок и мо­жем до лю­бо­го из них дос­ту­чать­ся как= до эле­мен­та мас­си­ва:

    

   &nb= sp;string s2 =3D (string)al[2];

    s2 бу­дет рав­но "test&qu= ot;. Есть толь­ко од­но не­удоб­с­т­во - Ar­ray­List всег­да воз­в­ра­ща­ет по­м= е­щен­ный в не­го объ­ект как obj­ect, по­это­му для нор­маль­ной ра­бо­ты нуж­но пре= ­об­ра­зо­вы­вать воз­в­ра­ща­емое зна­че­ние в нуж­ный тип... что не всег­да прос­то. Имен­но это пре­об­ра­зо­ва­ние и со­вер­ша­ет сло­во string, сто­ящее в круг­лых с= коб­ках пе­ред об­ра­ще­ни­ем al[2]. Под­роб­нее об этом - чуть ни­же.

    

    Типы дан­ных

    У каж­дой пе­ре­мен­ной есть с= вой тип дан­ных или прос­то тип, то же мож­но ска­зать о воз­в­ра­ща­емом лю­бой фун­к­ци­ей зна­че­нии (vo­id то­же тип, хо­тя и осо­бый­). В .NET все ти­пы дан­ных яв­ля­ют­ся до­чер­ни­ми от Obj­ect, у ко­то­ро­го есть все­го 4 фу= н­к­ции, за­то 2 из них нуж­ны очень час­то:

    Object.ToString() - воз­в­ра­щ= а­ет стро­ко­вое пред­с­тав­ле­ние об объ­ек­те (для чи­сел - стро­ку с чис­лом,= для слож­ных объ­ек­тов - не­кую стро­ку, опи­сы­ва­ющую глав­ную ин­фор­ма­цию= объ­ек­та; нап­ри­мер Fi­le­In­fo - класс ин­фор­ма­ции о фай­ле - в ме­то­де ToS­t­ri= ng() воз­в­ра­ща­ет пол­ный путь фай­ла.)

    Object.GetType() - воз­в­ра­ща= ­ет объ­ект клас­са Type, опи­сы­ва­ющий тип дан­ных, ко­то­ро­му при­над­ле­жи= т ис­сле­ду­емый объ­ект.

    Для срав­не­ния ти­пов есть сп= е­ци­аль­ный опе­ра­тор 'is'. Ис­поль­зу­ет­ся так:

    

   &nb= sp;if (var is int) { do so­met­hing }

    вместо var - имя пе­ре­мен­ной= ­, вмес­то int - тип дан­ных (имя клас­са) с ко­то­рым вы хо­ти­те срав­нить.<= span style=3D'color:black'>

    Рассмотрим при­мер из пре­ды­д= у­ще­го па­раг­ра­фа - у нас есть Ar­ray­List из 4 объ­ек­тов, 4 раз­ных ти­пов:

    

   &nb= sp;for (int i =3D 0; i ‹ al.Length; i ++) {

   &nb= sp;if (al[i] is int) { /*сде­лать что-то с це­лым чис­лом */ (int)al[i]}

   &nb= sp;else if (al[i] is do­ub­le} { /*сде­лать что-то с дроб­ным чис­лом*/ (do­ub­le)a= l[i]}

   &nb= sp;else if (al[i] is string} { /*сде­лать что-то со стро­кой­*/ (string)al[i]}=

   &nb= sp;else { Mes­sa­ge­Box.Show(al[i].ToS­t­ring()); } // по­ка­зать со­об­ще­ние со с= тро­ко­вым пред­с­тав­ле­ни­ем объ­ек­та

   &nb= sp;}

    В этом при­ме­ре мы про­бе­га­= ем цик­лом по спис­ку, смот­рим на тип оче­ред­ной пе­ре­мен­ной­, ес­ли это что-то пр= ос­тое - пре­об­ра­зу­ем и что-то де­ла­ем, ес­ли нет - по­ка­зы­ва­ем в со­об­ще­= нии стро­ко­вое пред­с­тав­ле­ние объ­ек­та.

    Теперь о пре­об­ра­зо­ва­нии. = Пре­об­ра­зо­ва­ни­ем ти­пов дан­ных на­зы­ва­ют два раз­ных про­цес­са.

    Один - это кон­вер­та­ция (Con= ­vert) - нас­то­ящая сме­на ти­па. Нап­ри­мер, прев­ра­тить do­ub­le в int - т.е. = от­б­ро­сить дроб­ную часть, или ок­руг­лить до бли­жай­ше­го це­ло­го. Или, еще нап­ри­= мер, прев­ра­тить do­ub­le в string - т.е. соз­дать стро­ку, где за­пи­са­но чис= ­ло, хра­ни­мое в пе­ре­мен­ной.

    Второй - это сме­на ти­па (cas= t) - без за­ме­ны дан­ных. Пе­ре­мен­ная ти­па obj­ect мо­жет на са­мом де­ле хр= а­нить лю­бой объ­ект, и что­бы ком­пи­ля­тор по­нял, что от не­го хо­тят - на­до = пре­об­ра­зо­вы­вать. Бы­ва­ют и бо­лее слож­ные ве­щи.

    Конвертация со­вер­ша­ет­ся вс= ег­да че­рез фун­к­цию, ко­то­рая зна­ет, как имен­но на­до пре­об­ра­зо­вы­вать = ти­пы. А все, что ка­са­ет­ся строк - на­до знать еще и нас­т­рой­ки куль­ту­ры дл= я ко­то­рой ве­дет­ся пре­об­ра­зо­ва­ние. Есть спе­ци­аль­ный класс Con­vert, ко­то­ры= й ве­да­ет кон­вер­та­ци­ей. Им и ре­ко­мен­ду­ет­ся поль­зо­вать­ся.

    

   &nb= sp;double d =3D 4.2;

   &nb= sp;string s1 =3D d.ToS­t­ring();

   &nb= sp;string s2 =3D Con­vert.ToS­t­ring(4.2);

    s1 и s2 - оди­на­ко­вы.

    Преобразование мо­жет быть: вс= тро­ен­ным (impli­cit - не­яв­ным), над­с­т­ро­ен­ным (expli­cit - яв­ным) и еще од­ним :)... на­зо­вем его ус­лов­ным.

    Встроенное - оно ли­бо есть, л= и­бо нет. Обес­пе­чи­ва­ет­ся соз­да­те­лем клас­са. Т.е. нап­ри­мер int в do­ub= ­le пре­об­ра­зу­ет­ся лег­ко, са­мим ком­пи­ля­то­ром, а вот на­обо­рот - толь= ­ко че­ло­ве­ком.

    Надстроенное - это то, что пи­= шет­ся в скоб­ках пе­ред име­нем пе­ре­мен­ной. Нап­ри­мер, мож­но вот так вот сде= ­лать:

    

   &nb= sp;CheckBox cb =3D new Chec­k­Box();

   &nb= sp;object obj =3D cb;

   &nb= sp;((CheckBox)obj).Checked =3D true;

    Условное ра­бо­та­ет толь­ко с= объ­ек­та­ми клас­сов и не ра­бо­та­ет с прос­ты­ми ти­па­ми дан­ных, кро­ме string. Выг= ­ля­дит оно как опе­ра­тор 'as' - 'как' (рас­смат­ри­вать как).

    

   &nb= sp;string s =3D al[2] as string;

    Оператор го­во­рит ком­пи­ля­т= о­ру, что под­со­вы­ва­емую ему пе­ре­мен­ную на­до рас­смат­ри­вать "как&qu= ot; ука­зан­ный тип дан­ных.

    

Гла­ва 6. При­мер ко­да.

  &nb= sp; 

    Итак, для пер­вой час­ти, я ду= ­маю дос­та­точ­но. С тем, что уже бы­ло рас­смот­ре­но, мож­но на­чи­нать прог­= рам­ми­ро­вать, а зна­ющие дру­гой язык дол­ж­ны бы­ли уже по­лу­чить пред­с­тав­ле­ние о C= #. Ос­та­лись не­рас­смот­рен­ны­ми еще мно­гие ве­щи, вклю­чая не­ко­то­рые и= з ос­нов­ных, но в си­лу то­го, что они нес­коль­ко слож­нее - я рас­смот­рю их в сле­ду­= ющих гла­вах.

    

    Пример ко­да:

    

   &nb= sp;namespace tst_form2 //за­да­ем na­mes­pa­ce

   &nb= sp;{

   &nb= sp;public de­le­ga­te do­ub­le De­le­ga­te1(); // в нем дек­ла­ри­ру­ем де­ле­га­та

   &nb= sp;public ab­s­t­ract class MyPa­ren­t­C­lass // соз­да­ем класс, аб­с­т­рак­т­ный

   &nb= sp;: Obj­ect, tst_form3.IIn­ter­fa­ce1 // по­рож­ден­ный от клас­са Obj­ect с под­к­лю­че= н­ным ин­тер­фей­сом tst_form3.IIn­ter­fa­ce1

   &nb= sp;{

   &nb= sp;private int int1; // внут­рен­нее по­ле, дос­туп­ное толь­ко внут­ри клас­са

   &nb= sp;protected int Int1 { // свой­ст­во для это­го по­ля, дос­туп­ное нас­лед­ни­кам<= /o:p>

   &nb= sp;get { re­turn int1; } // воз­в­рат­ный код

   &nb= sp;set { //уста­но­воч­ный код

   &nb= sp;if (va­lue › 0) { // про­вер­ка на зна­че­ние

   &nb= sp;int1 =3D va­lue; // ес­ли прош­ли - ус­та­но­вить зна­че­ние

   &nb= sp;}

   &nb= sp;else throw new Ar­gu­men­tEx­cep­ti­on("Va­lue must be › 0", "Int1"); // ес­ли нет - ки­нуть ошиб­ку

   &nb= sp;}

   &nb= sp;}

   &nb= sp;public do­ub­le d1; // от­к­ры­тые по­ля

   &nb= sp;public do­ub­le d2; //

   &nb= sp;public MyPa­ren­t­C­lass() { //основ­ной кон­с­т­рук­тор клас­са=

   &nb= sp;int1 =3D 0; // ини­ци­али­зи­ру­ем пе­ре­мен­ную на­чаль­ным зна­че­ни­ем

   &nb= sp;}

   &nb= sp;public MyPa­ren­t­C­lass(do­ub­le inD1, do­ub­le inD2) // до­пол­ни­тель­ный кон­с= ­т­рук­тор клас­са

   &nb= sp;: this() { // ко­то­рый сна­ча­ла за­пус­ка­ет ос­нов­ной кон­с­т­рук­тор и т= оль­ко по­том ис­пол­ня­ет­ся

   &nb= sp;d1 =3D inD1; // ини­ци­али­зи­ру­ем пе­ре­мен­ные вхо­дя­щи­ми ар­гу­мен­та­ми

   &nb= sp;d2 =3D inD2; // ес­ли ар­гу­мен­тов нет - пе­ре­мен­ные ини­ци­али­зи­ру­ют­ся са­= ми, стан­дар­т­ны­ми зна­че­ни­ями

   &nb= sp;}

   &nb= sp;protected ab­s­t­ract do­ub­le Get­Sum(); // шаб­лон фун­к­ции для ини­ци­али­за­ции = в до­чер­них клас­сах

   &nb= sp;#region IIn­ter­fa­ce1 Mem­bers // фун­к­ции ин­тер­фей­са

   &nb= sp;public ab­s­t­ract do­ub­le Get­Sub­s­t­ract(); // фун­к­ция ин­тер­фей­са - то­же толь­ко шаб­лон

   &nb= sp;#endregion

   &nb= sp;}

   &nb= sp;namespace tst_form3 // объ­яв­ля­ем еще na­mes­pa­ce внут­ри tst_form2

   &nb= sp;{

   &nb= sp;public class MyChil­d­C­lass //соз­да­ем класс

   &nb= sp;: tst_form2.MyPa­ren­t­C­lass //по­рож­ден­ный от tst_form2.MyPa­ren­t­C­lass=

   &nb= sp;{

   &nb= sp;private De­le­ga­te1 get­Sum­Del; // дек­ла­ри­ру­ем объ­ект де­ле­га­та=

   &nb= sp;internal MyChil­d­C­lass() //основ­ной кон­с­т­рук­тор

   &nb= sp;: ba­se() { // за­пус­ка­ющий ро­ди­тель­с­кий и толь­ко по­том ис­пол­ня­ющий­ся

   &nb= sp;getSumDel =3D new De­le­ga­te1(Get­Sum); // ини­ци­али­зи­ру­ем объ­ект де­ле­га­та -= на фун­к­цию Get­Sum

   &nb= sp;}

   &nb= sp;internal MyChil­d­C­lass(do­ub­le inD1, do­ub­le inD2) //до­пол­ни­тель­ный кон­с­т­= рук­тор

   &nb= sp;: ba­se(inD1, inD2) { // за­пус­ка­ющий до­пол­ни­тель­ный ро­ди­тель­с­кий кон­с­т­рук­т= ор

   &nb= sp;getSumDel =3D new De­le­ga­te1(Get­Sum); // ини­ци­али­зи­ру­ющий объ­ект де­ле­га­та=

   &nb= sp;}

   &nb= sp;#region in­he­ri­ted mem­bers // унас­ле­до­ван­ные фун­к­ции=

   &nb= sp;protected over­ri­de do­ub­le Get­Sum() { // оп­ре­де­ля­ем ро­ди­тель­с­кую аб­с­т­р= ак­т­ную фун­к­цию

   &nb= sp;return d1 + d2; //воз­в­ра­ща­ем сум­му

   &nb= sp;}

   &nb= sp;public over­ri­de do­ub­le Get­Sub­s­t­ract() { // оп­ре­де­ля­ем ро­ди­тель­с­кую= аб­с­т­рук­т­ную фун­к­цию

   &nb= sp;return d1 - d2; // воз­в­ра­ща­ем раз­ность

   &nb= sp;}

   &nb= sp;#endregion

   &nb= sp;internal vo­id Chan­ge­Int1(int inInt) { // соз­да­ем фун­к­цию для сме­ны зна­че­ни= я

   &nb= sp;Int1 =3D inInt;

   &nb= sp;}

   &nb= sp;public do­ub­le TryTo­Get­Sum() { //фун­к­ция по­лу­че­ния сум­мы

   &nb= sp;return Sta­tic­Func.Get­Sum­F­rom­C­hil­d­C­lass(get­Sum­Del); // вы­зы­ва­ем ста­= тич­ную фун­к­цию дру­го­го клас­са с де­ле­га­том для Get­Sum

   &nb= sp;}

   &nb= sp;}

   &nb= sp;public in­ter­fa­ce IIn­ter­fa­ce1 //интер­фей­с

   &nb= sp;{

   &nb= sp;double Get­Sub­s­t­ract(); // фун­к­ции ин­тер­фей­сов объ­яв­ля­ют­ся без мо­ди­ф= и­ка­то­ров

   &nb= sp;}

   &nb= sp;}

   &nb= sp;internal class Sta­tic­Func //класс для на­шей ста­тич­ной фун­к­ции

   &nb= sp;{

   &nb= sp;public sta­tic do­ub­le Get­Sum­F­rom­C­hil­d­C­lass(De­le­ga­te1 del) {

   &nb= sp;return del.Invo­ke(); // воз­в­ра­ща­ем ре­зуль­тат вы­зо­ва фун­к­ции, при­пи­сан= ­ной к де­ле­га­ту

   &nb= sp;}

   &nb= sp;public enum Sup­por­ted­Lan­gu­ages //прос­той Enum

   &nb= sp;{

   &nb= sp;Русский =3D 1,

   &nb= sp;English =3D 10,

   &nb= sp;German =3D 11

   &nb= sp;}

   &nb= sp;public sta­tic obj­ect[] obj­Ar­ray =3D {1, 2.4, "string", Sup­por­ted­L= an­gu­ages.Рус­ский }; // ста­тич­ный мас­сив

   &nb= sp;}

   &nb= sp;}//закрываем na­mes­pa­ce tst_form2

    Для тес­т­ро­ва­ния - на­пи­ше= м еще та­кую фун­к­цию в лю­бом дру­гом na­mes­pa­ce... хоть в ос­нов­ном, соз­да= н­ном ав­то­ма­ти­чес­ки при соз­да­нии про­ек­та:

    

   &nb= sp;private vo­id but­ton1_Click(obj­ect sen­der, Even­tArgs e) { //при на­жа­тии на кн= оп­ку

   &nb= sp;tst_form2.tst_form3.MyChildClass mcc =3D new tst_form2.tst_form3.MyChil­d­C­lass(1.2, 2.4); // соз­да­ем объ= ­ект на­ше­го клас­са с вве­ден­ны­ми зна­че­ни­ями

   &nb= sp;double tmp_do­ub­le =3D mcc.TryTo­Get­Sum(); //вер­нет 3.6

   &nb= sp;tmp_double =3D mcc.Get­Sub­s­t­ract(); //вер­нет -1.2

   &nb= sp;/*mcc.ChangeInt1(-2); //про­изой­дет ошиб­ка - не­вер­ный па­ра­метр*/

   &nb= sp;mcc.ChangeInt1(2); //int1 ста­нет ра­вен 2

   &nb= sp;int i1 =3D (int)tst_form2.Sta­tic­Func.Sup­por­ted­Lan­gu­ages.English; // i1 = =3D 10

   &nb= sp;string s =3D tst_form2.Sta­tic­Func.Sup­por­ted­Lan­gu­ages.English.ToS­t­ring();/= /s=3D"English"

   &nb= sp;s =3D "";

   &nb= sp;for (int i =3D 0; i ‹ tst_form2.Sta­tic­Func.obj­Ar­ray.Length; i++) {

   &nb= sp;s +=3D tst_form2.Sta­tic­Func.obj­Ar­ray[i].ToS­t­ring() + "\t";

   &nb= sp;if (tst_form2.Sta­tic­Func.obj­Ar­ray[i] is int) s +=3D "\n";

   &nb= sp;else {

   &nb= sp;switch (tst_form2.Sta­tic­Func.obj­Ar­ray[i].Get­T­y­pe().ToS­t­ring()) {

   &nb= sp;case "System.Do­ub­le":

   &nb= sp;s +=3D Math.Ro­und((do­ub­le)tst_form2.Sta­tic­Func.obj­Ar­ray[i]).ToS­t­ring() + "\n";

   &nb= sp;break;

   &nb= sp;case "tst_form2.Sta­tic­Func+Sup­por­ted­Lan­gu­ages":

   &nb= sp;s +=3D ((int)(tst_form2.Sta­tic­Func.Sup­por­ted­Lan­gu­ages)tst_form2.Sta­tic­Fun= c.obj­Ar­ray[i]).ToS­t­ring();

   &nb= sp;break;

   &nb= sp;default:

   &nb= sp;s +=3D "\n";

   &nb= sp;break;

   &nb= sp;}

   &nb= sp;}

   &nb= sp;} // цикл за­вер­ша­ет­ся s =3D "1

   &nb= sp;//2.4 2

   &nb= sp;//string

   &nb= sp;//Русский 1"

   &nb= sp;}

    

    Немного до­пол­не­ний и ком­ме= н­та­ри­ев:

    Если у фун­к­ции есть мо­ди­фи= ­ка­тор over­ri­de - ее мож­но пе­ре­оп­ре­де­лять во всех до­чер­них клас­сах, лю­= бо­го по­ко­ле­ния.

    Если вы хо­ти­те сра­зу оп­ре­= де­лить фун­к­цию, но дать ей воз­мож­ность быть пе­ре­оп­ре­де­лен­ной в нас­лед­н= и­ках - прис­вой­те мо­ди­фи­ка­тор vir­tu­al.

    Последовательное вы­пол­не­ние= ус­ло­вий в switch бло­ке воз­мож­но толь­ко ес­ли ca­se ус­ло­вия оп­ре­де­ле­ны чис= ­ла­ми.

    По по­во­ду плю­си­ка в име­ни= ти­па Enum - Enum, по ло­ги­ке, на­до объ­яв­лять в na­mes­pa­ce, на­рав­не с кла= с­са­ми. Од­на­ко, мож­но их за­пи­хать и внутрь клас­са. Ес­ли их объ­явить в na­me= s­pa­ce - имя ти­па бу­дет пос­т­ро­ено как обыч­но, а ес­ли внут­ри клас­са - то ч= е­рез плю­сик.

    

Часть 2.

  &nb= sp; Чуть бо­лее слож­ные ве­щи в язы­ке C# и плат­фор­ме .NET.

Глава 1. Модификаторы аргументов. Регулярные выраже= ния.

  &nb= sp; 

    Рассмотрим мо­ди­фи­ка­то­р= ы ар­гу­мен­тов фун­к­ций.

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

    Пример:

    

   &nb= sp;private vo­id Func1(ref string str) {

   &nb= sp;str =3D "но­вая стро­ка";

   &nb= sp;}

   &nb= sp;private vo­id Ma­in­Func() {

   &nb= sp;string str =3D "стро­ка";

   &nb= sp;Func1(ref str); // str =3D "но­вая стро­ка"

   &nb= sp;}

    out - пе­ре­да­ет в ка­= чес­т­ве ар­гу­мен­та ссыл­ку на не­ини­ци­али­зи­ро­ван­ный объ­ект. Та­ким об­ра­з= ом вы по­лу­ча­ете воз­мож­ность воз­в­ра­щать мно­жес­т­вен­ные ре­зуль­та­ты= ра­бо­ты фун­к­ции.

    Пример:

    

   &nb= sp;private vo­id Func1(out string re­sult1, out int re­sult2, out obj­ect re­sult3) {<= o:p>

   &nb= sp;result1 =3D 10;

   &nb= sp;result2 =3D "ре­зуль­тат";

   &nb= sp;result3 =3D new obj­ect();

   &nb= sp;}

   &nb= sp;private vo­id Ma­in­Func() {

   &nb= sp;int r1;

   &nb= sp;string r2;

   &nb= sp;object r3;

   &nb= sp;Func1(out r1, out r2, out r3);

   &nb= sp;}

    Разница меж­ду ref и out толь­= ко в том, что out не тре­бу­ет ини­ци­али­за­ции ар­гу­мен­та до вы­зо­ва фун­к­= ции, а тре­бу­ет ее внут­ри фун­к­ции. ref - на­обо­рот, тре­бу­ет пе­ре­да­чи и= ни­ци­али­зи­ро­ван­но­го объ­ек­та.

    

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

    Например так:

    

   &nb= sp;private vo­id Func1(byte b1, pa­ram int[] da­ta) {}

    Если вам не­об­хо­ди­мо, что­б= ы ар­гу­мен­ты бы­ли раз­но­тип­ны - счи­тай­те их ти­пом obj­ect. Еще один мо­мент - вмес= ­то спис­ка ар­гу­мен­тов в стро­ке, мож­но пе­ре­да­вать мас­сив... но тут на­= до быть ос­то­рож­ным:

    

   &nb= sp;private vo­id Func1(byte b1, pa­ram obj­ect[] da­ta) { }

   &nb= sp;private vo­id Ma­in­Func() {

   &nb= sp;Func1(1, 1, "str", new obj­ect()); // все нор­маль­но. da­ta - мас­сив ти­= па obj­ect из трех чле­нов.

   &nb= sp;Func1(2, new obj­ect[] {1, "str", new obj­ect()}); // все нор­маль­но, da­= ta - как в пре­ды­ду­щем ва­ри­ан­те.

   &nb= sp;Func1(3, 1, new obj­ect[]{"str", new obj­ect()}); // da­ta - мас­сив ти­па= obj­ect из двух чле­нов, вто­рой - мас­сив ти­па obj­ect из 2х чле­нов.<= /span>

   &nb= sp;}

    

 Регулярные вы­ра­же­ния

    В об­щем ви­де ре­гу­ляр­ные в= ы­ра­же­ния ( =3D re­gu­lar ex­p­res­si­ons =3D Re­gEx) - это шаб­лон тек­с­то­вой стро= ­ки. Поч­ти все за­да­чи ка­са­ющи­еся по­ис­ка и за­ме­ны в стро­ках пред­поч­ти­тель­= но ре­шать че­рез них. За­да­чи по­ис­ка и за­ме­ны вклю­ча­ют в се­бя по­иск = тек­с­та, ре­дак­ти­ро­ва­ние тек­с­та, пре­об­ра­зо­ва­ние тек­с­та (фор­ма­ти­ро­ва= ­ние), вы­би­ра­ние по­лез­ной ин­фор­ма­ции из тек­с­та для пос­ле­ду­юще­го ис­п= оль­зо­ва­ния и пр. Поль­зо­вать­ся ре­гу­ляр­ны­ми вы­ра­же­ни­ями име­ет смысл ког­да о= бъ­ем тек­с­та дос­та­точ­но ве­лик. Для ра­бо­ты с ре­гу­ляр­ны­ми вы­ра­же­ни­я= ми су­щес­т­ву­ет класс Re­gex (System.Text.Re­gu­la­rEx­p­res­si­ons.Re­gex).=

    Использоваться этот класс мо­ж= ет тре­мя пу­тя­ми:

    1. поль­зо­вать­ся ста­ти­чес­= ки­ми ме­то­да­ми клас­са. Пред­поч­ти­тель­но, ес­ли прог­рам­ме это ред­ко нуж­= но, и не при каж­дом за­пус­ке, а так­же, ес­ли са­мо вы­ра­же­ние ис­поль­зу­е= т­ся толь­ко один раз под­ряд.

    2. Соз­да­вать объ­ект клас­са= re­gex в не­ком­пи­ли­ру­емом ви­де (флаг Com­pi­led не ус­та­нов­лен) и поль­зо­в= ать­ся его фун­к­ци­ями. Пред­поч­ти­тель­но ес­ли прог­рам­ме это ред­ко нуж­но, = и не при каж­дом за­пус­ке, но вы­ра­же­ние ис­поль­зу­ет­ся нес­коль­ко раз под= ­ряд.

    3. Соз­да­вать объ­ект клас­са= re­gex в ком­пи­ли­ру­емом ви­де (флаг Com­pi­led ус­та­нов­лен) и поль­зо­вать­ся= его фун­к­ци­ями. Пред­поч­ти­тель­но ес­ли прог­рам­ма час­то поль­зу­ет­ся вы= ­ра­же­ни­ем.

    

    Регулярное вы­ра­же­ние сос­то= ­ит из шаб­ло­на и оп­ций. С оп­ци­ями ра­зоб­рать­ся до­воль­но прос­то, а вот ша= б­ло­ны - это от­дель­ный язык. По по­во­ду за­да­ния шаб­ло­нов мо­гу толь­ко по­с= о­ве­то­вать поль­зо­вать­ся раз­ны­ми вспо­мо­га­тель­ны­ми прог­рам­ма­ми ти­па Ex­p­r= es­so и пр.

    Возьмем для при­ме­ра прос­той= шаб­лон:

    

   &nb= sp;(?‹Protocol›\w+):\/\/(?‹Domain›[\w\.]+)\/?\S*

    Этот шаб­лон вы­дер­ги­ва­ет и= з пред­ло­жен­но­го тек­с­та все URL, и сох­ра­ня­ет их с вы­де­ле­ни­ем про­то­ко­ла и до­ме­н= а.

    Код для ис­поль­зо­ва­ния:

    

   &nb= sp;void Re­gex­Match() {

   &nb= sp;string inStr =3D "blah-blah-blah so­met­hing he­re http://www.do­ma­in.com/in= dex.cgi and so­me com­ments he­re and fi­le he­re ftp://ftp.do­ma­in123.info/re­po­= si­tory/";

   &nb= sp;StringBuilder sb =3D new Strin­g­Bu­il­der();

   &nb= sp;Regex r =3D new Re­gex(@"(?‹Pro­to­col›\w+):\/\/(?‹Do­ma­in›[\w\.]+)\/?\S*&q= uot;, Re­ge­xOp­ti­ons.Igno­re­Ca­se | RegexOptions.Singleline);<= /span>

   &nb= sp;for (Match m =3D r.Mat­ch(inStr); m.Suc­cess; m =3D m.Nex­t­Match()) {

   &nb= sp;sb.AppendFormat("Найдено URL: {0}, про­то­кол: {1}, до­мен: {2}\r\n", m.Va­lue, m.Gro­ups["= ;Pro­to­col"].Va­lue, m.Gro­ups["Do­ma­in"].Va­lue);

   &nb= sp;}

   &nb= sp;textBox1.Text =3D sb.ToS­t­ring();

   &nb= sp;}

    Результат бу­дет:

    

   &nb= sp;Найдено URL: http://www.do­ma­in.com/index.cgi, про­то­кол: http, до­мен: www.do­ma= ­in.com

   &nb= sp;Найдено URL: ftp://ftp.do­ma­in123.info/re­po­si­tory/, про­то­кол: ftp, до­мен: ft= p.do­ma­in123.info

    

    Примеров мо­жет быть мно­жес­т= ­во, луч­ше пос­мот­ри­те шаб­ло­ны в дей­ст­вии... а в Ex­p­res­so есть сис­те­= ма ин­те­рак­тив­но­го сос­тав­ле­ния шаб­ло­на... очень по­лез­но для обу­че­= ния, да и при­ме­ров у них мно­го.

    

Гла­ва 2. Ге­не­ало­гия клас­сов.

  &nb= sp; 

    Итак, по­го­во­рим ког­да ко­г= о и как нуж­но по­рож­дать.

    Один из пос­ту­ла­тов сов­ре­м= ен­но­го вы­со­ко­уров­не­во­го прог­рам­ми­ро­ва­ния, не всег­да вер­ных, впро­чем,= гла­сит, что "Чем мень­ше оди­на­ко­вых бло­ков ко­да в прог­рам­ме - тем луч­ш= е". Пер­вое след­с­т­вие из это­го пос­ту­ла­та - блок ко­да, ко­то­рый ис­поль= ­зу­ет­ся боль­ше, чем в од­ном мес­те прог­рам­мы дол­жен быть вы­не­сен в от­дель­н= ую фун­к­цию. Впро­чем, это к те­ме не от­но­сит­ся. Вто­рое след­с­т­вие - на= ­бор пе­ре­мен­ных/фун­к­ций­, ко­то­рые ис­поль­зу­ют­ся боль­ше чем в од­ном к= лас­се дол­ж­ны быть вы­де­ле­ны в от­дель­ный ро­ди­тель­с­кий класс. В об­щем-то, этим пра­ви­лом мож­но ру­ко­вод­с­т­во­вать­ся при оп­ре­де­ле­нии "н= у­жен ли вам не­кий ро­ди­тель для ва­ших клас­сов?", ес­ли у вас их мно­го.=

    

    Рассмотрим мо­дель­ную си­ту­а= цию с мно­жес­т­вом клас­сов, и оп­ре­де­лим ка­кие им нуж­ны род­с­т­вен­ные свя= ­зи.

    

    Довольно клас­си­чес­кий при­м= ер - эле­мен­ты уп­рав­ле­ния. Рас­смот­рим 4 эле­мен­та уп­рав­ле­ния: кноп­ка, тек­с­= то­вое по­ле, па­нель и груп­па. У всех этих эле­мен­тов дол­ж­ны быть свой­ст­ва:= по­ло­же­ние, раз­мер, со­бы­тия на­жа­тия, фо­ку­са и еще мож­но мно­го вся­ких на­пи­са= ть для удоб­с­т­ва прог­рам­мис­тов. Ста­ло быть один пре­док, об­щий для всех= эле­мен­тов уп­рав­ле­ния на­шел­ся. Ду­ма­ем даль­ше - па­нель и груп­па дол­ж­ны иметь под­чи­нен­ные эле­мен­ты уп­рав­ле­ния, а так­же уметь прок­ру­чи­вать соб= ­с­т­вен­ную внут­рен­нюю об­ласть. Ста­ло быть у этих дво­их дол­жен быть еще об­щий пр= е­док. Итак струк­ту­ра по­лу­чить­ся при­мер­но та­кой­:

    

  &nb= sp; Ну вот вам об­щий прин­цип вы­де­ле­ния об­щих пред­ков.

    

    Рассмотрим воп­ро­сы нас­лед­с= ­т­ва, ог­ра­ни­че­ния на нас­лед­с­т­во, рас­т­ран­жи­ри­ва­ния нас­лед­с­т­ва и = сте­ри­ли­за­ции.

    

    Многое из то­го, о чем сей­час= пой­дет речь уже рас­ска­зы­ва­лось в час­ти 1 гла­ве 1 в раз­де­ле о дек­ла­ра­ци­= ях.

    Главное пра­ви­ло нас­ле­до­ва= ­ния - каж­дый по­то­мок нас­ле­ду­ет все. Т.е. все чле­ны клас­са, от­к­ры­тые для нас­ле­до­ва­ния, пе­ре­да­ют­ся каж­до­му по­том­ку в лю­бом ко­ле­не. Еди= н­с­т­вен­ное, что мо­жет по­ме­шать - пе­ре­оп­ре­де­ле­ние чле­на клас­са.

    

 По по­во­ду "как жить по­том­кам":<= /b>

    Итак, мо­ди­фи­ка­тор ab­s­= t­ract у клас­са го­во­рит о том, что сам класс ни­че­го сде­лать не мо­жет, толь­= ко нас­лед­с­т­во ос­та­лось. Бу­дем счи­тать его без­в­ре­мен­но по­чив­шим р= од­с­т­вен­ни­ком... тем са­мым дя­дей "са­мых чес­т­ных пра­вил", ко­то­рый од­на­ко = весь­ма чет­ко опи­сал как имен­но дол­ж­ны жить его по­том­ки. Тот же мо­ди­фи­ка­= тор у чле­на клас­са го­во­рит о том, что класс счи­та­ет этот член обя­за­тель= ­ным у сво­их по­том­ков, но сам тол­ком не зна­ет что это та­кое. Эда­кий стар­= ший род­с­т­вен­ник, зна­ющий как дол­ж­ны жить его по­том­ки, но сам жи­ву­щий по-дру­го­му.

    Модификатор vir­tu­al у= чле­на клас­са го­во­рит о том, что класс зна­ет что де­лать, од­на­ко до­пус­ка­ет иное тол­ко­ва­ние для по­том­ков. Хо­ро­ший род­с­т­вен­ник с ши­ро­ки­ми взгля­да­ми :).

    

 Теперь ка­са­тель­но дос­ту­па к нас­лед­с­т­= ву:

    модификатор pri­va­te о= бес­пе­чи­ва­ет неп­ри­кос­но­вен­ность нас­лед­с­т­ва. Дос­туп есть толь­ко из дру­гих уна= с­ле­до­ван­ных фун­к­ций. Нап­ри­мер, у ро­ди­те­ля есть от­к­ры­тая фун­к­ция pub­lic1= , и зак­ры­тая фун­к­ция pri­va­te1. По­то­мок мо­жет об­ра­тить­ся то= ль­ко к pub­lic1, но ес­ли внут­ри ко­да pub­lic1 есть об­ра­ще­ние= к pri­va­te1, то все бу­дет ра­бо­тать.

    модификатор pro­tec­ted= , в воп­ро­сах нас­лед­с­т­ва, эк­ви­ва­лен­тен pub­lic - нас­лед­с­т­во от­к­ры­то= для дос­ту­па. "Поль­зуй­тесь, род­с­т­вен­нич­ки до­ро­гие!"

    

 Теперь о соб­с­т­вен­ном мне­нии по­том­ков п= о по­во­ду нас­лед­с­т­ва.

    Вобщем-то, ник­то не ме­ша­ет = по­том­кам вы­би­рать из нас­лед­с­т­ва толь­ко пон­ра­вив­ши­еся час­ти. Да и по­сы­л= ать стар­ших с их со­ве­та­ми "как на­до жить" то­же. Лю­бая фун­к­ци= я, объ­яв­лен­ная vir­tu­al мо­жет быть пе­ре­оп­ре­де­ле­на мо­ди­фи­к= а­то­ром over­ri­de. Ес­ли очень на­до пе­ре­оп­ре­де­лить фун­к­цию не от­ме= ­чен­ную vir­tu­al - мож­но ис­поль­зо­вать мо­ди­фи­ка­тор new. Прав­= да с этим на­до быть ос­то­рож­ным - ес­ли уж вы нас­ле­ду­ете от ко­го-то, к ва= м бу­дут со­от­вет­с­т­вен­но от­но­сит­ся и мо­жет быть кон­ф­ликт с дру­ги­ми клас= ­са­ми, жду­щи­ми од­ной под­пи­си фун­к­ции, а на­па­ры­ва­ющи­ми­ся на дру­гую. Д= а и от род­ни не уй­дешь - до ори­ги­наль­но­го ва­ри­ан­та фун­к­ции все рав­но всег­да мож­но дос­ту­чать­ся, ес­ли знать что он есть. Из­нут­ри по­том­ка= дос­та­точ­но ис­поль­зо­вать клю­че­вое сло­во ba­se, а сна­ру­жи - пре­об­ра­зо­= вать тип объ­ек­та в тип пред­ка. Пред­по­ло­жим, фун­к­ция Func1 пе­ре­о= п­ре­де­ле­на в клас­се B, по­том­ке клас­са А с ис­поль­зо­ва­ние сло­ва <= b>new:

    

   &nb= sp;B b1 =3D new B();

   &nb= sp;b1.Func1(); // но­вый ва­ри­ант фун­к­ции.

   &nb= sp;((A)b1).Func1(); // ста­рый ва­ри­ант фун­к­ции

 И са­мое страш­ное - сте­ри­ли­за­ция.

    Очень прос­то де­ла­ет­ся, как= и все ужа­сы в на­шем ми­ре. Мо­ди­фи­ка­тор клас­са se­aled - сте­ри­ли­з= у­ет его, и у не­го уже ни­ког­да не бу­дет де­тей :(. Тот же мо­ди­фи­ка­тор, п= ри­ме­нен­ный к vir­tu­al фун­к­ции от­ме­ня­ет ее вир­ту­аль­ность.

    

 И на­пос­ле­док - крес­т­ные (отцы/ма­те­ри/д= я­ди/те­ти/феи и пр. фоль­к­лор­ные эле­мен­ты)

    В ро­ли крес­т­ных выс­ту­па­ю= т ин­тер­фей­сы. Их у каж­до­го клас­са мо­жет быть сколь­ко угод­но, и для то­го, что­бы им= еть крес­т­ных не обя­за­тель­но иметь ро­ди­те­лей. Ин­тер­фей­сы все очень ст= ро­гие - они точ­но зна­ют что дол­жен уметь де­лать их крес­т­ник, и не от­с­та­ю= т, по­ка он все­му это­му не на­учить­ся. При­чем все, че­му учит ин­тер­фей­с= обя­за­тель­но ос­та­ет­ся в дос­туп­ном нас­лед­с­т­ве (pub­lic). Ну а воз­мож­ность по­т= ом­ков ре­шать са­мим за­ви­сит толь­ко от пред­ка, но ни­как не от крес­т­но­го. = Это в клас­се-пред­ке оп­ре­де­ля­ет­ся бу­дет ли фун­к­ция с мо­ди­фи­ка­то­ром vir­tu­al или нет, да и что имен­но бу­дет де­лать фун­к­ция то­же оп­ре­де= ­ля­ет­ся там.

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

    каждое по­ле ин­тер­фей­са мо­= жет быть им­п­ле­мен­ти­ро­ва­но в клас­се внут­рен­ним и внеш­ним об­ра­зом

    

   &nb= sp;class Class1 : IIn­ter­fa­ce1 {

   &nb= sp;public vo­id in­ter­fa­ce­Func1() {}; // внут­рен­няя им­п­ле­мен­та­ция

   &nb= sp;public vo­id IIn­ter­fa­ce1.inter­fa­ce­Func2() {}; // внеш­няя им­п­ле­мен­та­ция=

   &nb= sp;}

    Имплементирование внеш­ним об­= ра­зом яв­но ука­зы­ва­ет на ис­точ­ник тол­ко­ва­ния ка­ко­го-ли­бо по­ля. Та­кой= под­ход поз­во­ля­ет из­бе­жать оши­бок при ком­пи­ля­ции и при пос­ле­ду­ющих об­р= а­ще­ни­ях. Кста­ти, за­пом­ни­те, ес­ли вам нуж­на от объ­ек­та фун­к­ция ка­ко­го-то = из его ин­тер­фей­сов - всег­да об­ра­щай­тесь к ней пред­ва­ри­тель­но пре­об= ­ра­зо­вав тип объ­ек­та в тип ин­тер­фей­са. Нап­ри­мер: объ­ект ob1 им­п­ле­мен­ти­р= у­ет нес­коль­ко ин­тер­фей­сов, в том чис­ле IIn­ter­fa­ce1, с нуж­ной нам фун­= к­ци­ей in­t­Func1:

    

   &nb= sp;((IInterface1)ob1).intFunc1();

    

    Этим вы да­ете по­нять, что ва= м нуж­на фун­к­ция in­t­Func1 имен­но в трак­тов­ке IIn­ter­fa­ce1, а не в чьей­-ни­= будь дру­гой. А то фун­к­цию On­Pa­int кто толь­ко не им­п­ле­мен­ти­ру­ет...

    

Глава 3. Исключения.

  &nb= sp; 

    Исключения (Excep­ti­ons) - эт= о то, что слу­ча­ет­ся, ког­да что-то неп­ра­виль­но. Нап­ри­мер, вы пы­та­етесь = от­к­рыть файл, ко­то­ро­го нет - сис­тем­ная биб­ли­оте­ка ки­нет ис­к­лю­че­ние &qu= ot;файл не най­ден".

    Исключения на­до уметь ки­дать (throw) и ло­вить (catch).

    Лично я знаю 3 ме­то­ди­ки ра­= бо­ты с ис­к­лю­че­ни­ями, каж­дая из ко­то­рых на­пи­са­на прог­рам­мис­том с бо= ль­шим опы­том и мно­го­мет­ро­вым спис­ком ре­га­лий­, при­чем каж­дая из них ссы= ­ла­ет­ся на ка­ко­го-ни­будь ве­ли­ко­го гу­ру (одно­го из соз­да­те­лей .net, нап­р= и­мер). На­до за­ме­тить, что эти три ме­то­ди­ки во мно­гом про­ти­во­ре­чат друг = дру­гу. Вы­вод - ни­ка­кой "пра­виль­ной­" или "луч­шей­" ме­то= ­ди­ки ра­бо­ты с ис­к­лю­че­ни­ями нет. Есть толь­ко не­ко­то­рые пра­ви­ла, и чь= е-то мне­ние :).

    Рассмотрим ос­но­вы ра­бо­ты с= ис­к­лю­че­ни­ями и те пра­ви­ла, ко­то­рые оди­на­ко­вы во всех трех ме­то­ди­ках.

    

    Как ки­дать ис­к­лю­че­ния<= /b>

    Для ки­да­ния су­щес­т­ву­ет к= лю­че­вое сло­во throw.

    

   &nb= sp;throw new Ex­cep­ti­on("Про­изош­ла ошиб­ка...");=

    

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

    Например, вы пи­ше­те фун­к­ци= ю рас­че­та че­го-ни­будь по двум дроб­ным вхо­дя­щим ар­гу­мен­там, но вам нуж­но что­= бы ар­гу­мен­ты бы­ли боль­ше 1, ина­че ни­че­го не пос­чи­та­ет­ся... Вы мо­ж= е­те про­ве­рять ар­гу­мен­ты и ес­ли они мень­ше - воз­в­ра­щать do­ub­le.NaN. = Так ус­т­ро­ены ма­те­ма­ти­чес­кие фун­к­ции клас­са Math. А мо­же­те сде­лать так:

    

   &nb= sp;private do­ub­le func1(do­ub­le arg1, do­ub­le arg2) {

   &nb= sp;if (arg1 ‹=3D 1) throw new Ar­gu­men­tEx­cep­ti­on("Аргу­мент дол­жен быт= ь боль­ше 1", "arg1");

   &nb= sp;if (arg2 ‹=3D 1) throw new Ar­gu­men­tEx­cep­ti­on("Аргу­мент дол­жен быт= ь боль­ше 1", "arg2");

   &nb= sp;// рас­чет

   &nb= sp;}

    

Такой код позволит указать программисту на ошибки. Собственно примерно так и надо писать библиотеки, которыми будут пользовать= ся другие.

    Еще си­ту­ация ког­да на­до ки= ­дать ис­к­лю­че­ния - ког­да вы пе­рех­ва­ты­ва­ете ис­к­лю­че­ние в сво­ей фун­= к­ции, но вам на­до до­ба­вить к не­му ка­кую-то ин­фор­ма­цию.... нап­ри­мер, так= :

    

   &nb= sp;try {

   &nb= sp;using (Fi­leS­t­re­am fs =3D Fi­le.Open­Re­ad(@"C:\\temp.txt")) {<= /o:p>

   &nb= sp;}

   &nb= sp;}

   &nb= sp;catch (Fi­le­Not­Fo­un­dEx­cep­ti­on ex) {

   &nb= sp;throw new In­va­li­dO­pe­ra­ti­onEx­cep­ti­on("Ошиб­ка от­к­ры­тия фай­ла в = мо­ей фун­к­ции...", ex);

   &nb= sp;}

    

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

    

    Как ло­вить ис­к­лю­че­ния<= /b>

    Для от­ло­ва ис­к­лю­че­ний ис= ­поль­зу­ет­ся кон­с­т­рук­ция try {} catch {} fi­nal­ly {}.

    Блок try (поп­ро­бо­ват= ь) со­дер­жит код, ко­то­рый мо­жет выз­вать ошиб­ку.

    Блок catch (пой­мать) с= о­дер­жит код об­ра­бот­ки ошиб­ки. Та­ких бло­ков мо­жет быть нес­коль­ко - на каж­д= ый тип ошиб­ки, ко­то­рые вы жде­те.

    Блок fi­nal­ly (на­пос­= ле­док) не­обя­за­те­лен, в не­го по­ме­ща­ет­ся код, ко­то­рый вы­пол­ня­ет­ся в л= ю­бом слу­чае - за­кон­чил­ся ли блок try нор­маль­но, или ки­нул ис­к­лю­че­ние.= Осо­бая фи­ча бло­ка fi­nal­ly - он вы­пол­ня­ет­ся да­же ес­ли фун­к­ция уже вер­н= у­ла зна­че­ние.

    Пример:

    

   &nb= sp;try {

   &nb= sp;// опас­ный код

   &nb= sp;}

   &nb= sp;catch (Argu­men­tEx­cep­ti­on aex) {

   &nb= sp;MessageBox.Show(aex.Message, "Error oc­cu­red");

   &nb= sp;return 1;

   &nb= sp;}

   &nb= sp;catch (Excep­ti­on ex) {

   &nb= sp;throw new In­va­li­dO­pe­ra­ti­onEx­cep­ti­on("Cus­tom mes­sa­ge...", e= x);

   &nb= sp;}

   &nb= sp;finally {

   &nb= sp;// зак­рыть по­то­ки и пр.

   &nb= sp;}

    К сло­ву:

    Вместо бло­ка fi­nal­ly мож­но= ис­поль­зо­вать кон­с­т­рук­цию using () {}. Эта кон­с­т­рук­ция поз­во­ля­ет убе­ди= ть­ся, что все ре­сур­сы за­ня­тые не­ким объ­ек­том бу­дут пе­ре­ки­ну­ты в му­сор или уда­ле­ны, в за­ви­си­мос­ти от ти­па, по за­вер­ше­нии кус­ка ко­да, в= не за­ви­си­мос­ти от ре­зуль­та­тов это­го са­мо­го ко­да.

    пример:

    

   &nb= sp;using (MyClass obj­ect1 =3D new MyClass()) {

   &nb= sp;// код, ис­поль­зу­ющий obj­ect1

   &nb= sp;}

    Даже ес­ли код ки­нет ис­к­лю­= че­ние - obj­ect1 бу­дет уда­лен. Для бло­ка using не обя­за­тель­на ини­ци­али­за= ­ция а бло­ке, мож­но и так:

    

   &nb= sp;MyClass ob1 =3D new MyClass();

   &nb= sp;using (ob1) {}

    Конструкция using мо­жет выс­т= у­пать за­ме­ни­те­лем бло­ка fi­nal­ly толь­ко ес­ли вам в этом бло­ке на­до бы­ло уда­лить один объ­ект. Впро­чем, это очень час­то слу­ча­ет­ся. С дру­гой с= то­ро­ны, она весь­ма по­лез­на, ког­да код, ис­поль­зу­ющий объ­ект име­ет нес­коль­= ко то­чек воз­в­ра­ще­ния. Что­бы точ­но не за­быть снес­ти объ­ект - про­ще з= ак­лю­чить код в блок using. При­мер:

    

   &nb= sp;MyClass ob1 =3D new MyClass();

   &nb= sp;// что-то сде­лать с ob1

   &nb= sp;if (ob1.pro­perty1 =3D=3D 1) {

   &nb= sp;ob1.Dispose();

   &nb= sp;return 1;

   &nb= sp;}

   &nb= sp;// что-то еще сде­лать

   &nb= sp;if (ob1.pro­perty1 =3D=3D 2) {

   &nb= sp;ob1.Dispose();

   &nb= sp;return 2;

   &nb= sp;}

    Чтобы не пи­сать каж­дый раз D= is­po­se, мож­но сде­лать так:

    

   &nb= sp;using (MyClass ob1 =3D new MyClass()) {

   &nb= sp;// что-то сде­лать с ob1

   &nb= sp;if (ob1.pro­perty1 =3D=3D 1) re­turn 1;

   &nb= sp;// что-то еще сде­лать

   &nb= sp;if (ob1.pro­perty1 =3D=3D 2) re­turn 2;

   &nb= sp;}

    

    Ситуации в ко­то­рых на­до = ло­вить ис­к­лю­че­ния

    В об­щем ви­де это зву­чит как "Ло­вить ис­к­лю­че­ния на­до ког­да что-то мо­жет пой­ти не так"= . Ни о чем не го­во­ря­щая фра­за. Ес­ли быть бли­же к ре­аль­нос­ти - ло­вить и= с­к­лю­че­ния на­до тог­да, ког­да вы не уве­ре­ны в об­ра­ба­ты­ва­емых дан­ных. Эта не­= уве­рен­ность мо­жет быть по по­во­ду ти­па дан­ных, зна­че­ния дан­ных и т. д. Стан­дар­= т­ные си­ту­ации, ког­да вы не мо­же­те быть уве­ре­ны:

    Все что ка­са­ет­ся вво­да дан= ­ных от поль­зо­ва­те­ля - ник­то не зна­ет что вве­дет поль­зо­ва­тель.

    Любое чте­ние из по­то­ка, ес­= ли толь­ко это не по­ток в па­мя­ти - кто зна­ет что там в фай­ле на са­мом де= ­ле.

    Любая се­те­вая опе­ра­ция - в= ы не мо­же­те быть уве­ре­ны, что в нуж­ный мо­мент не обор­вет­ся ка­бель или с= и­сад­мин не ре­шит по­шу­тить.

    Любое об­ра­ще­ние к внеш­ним = ба­зам дан­ных на­до рас­смат­ри­вать как се­те­вую опе­ра­цию.

    Любой вы­вод в по­ток - мес­то= на дис­ке за­кан­чи­ва­ет­ся в са­мый не­под­хо­дя­щий мо­мент.

    

    Общие ме­то­ди­ки

    Исключения - не па­на­цея и не что-то очень хо­ро­шее, чем на­до час­то поль­зо­вать­ся. Они жрут по­ряд­к= ом ре­сур­сов, так что ре­ко­мен­ду­ет­ся про­ве­рять зна­че­ния ар­гу­мен­тов= са­мим, а не по­ла­гать­ся на выб­рос ис­к­лю­че­ния.

    Пример:

    

   &nb= sp;public string func1 (string inStr) {

   &nb= sp;if (string.IsNul­lO­rEm­p­ty(inStr)) { re­turn null; }

   &nb= sp;}

    такой код быс­т­рее, чем:

    

   &nb= sp;public string func1 (string inStr) {

   &nb= sp;if (string.IsNul­lO­rEm­p­ty(inStr) { throw new Ar­gu­men­tEx­cep­ti­on(); }

   &nb= sp;}

    и уж, тем бо­лее, чем:

    

   &nb= sp;public string func1 (string inStr) {

   &nb= sp;try {

   &nb= sp;// сде­лать что-то с inStr

   &nb= sp;}

   &nb= sp;catch (Argu­men­tEx­cep­ti­on ex) {

   &nb= sp;throw ex;

   &nb= sp;}

   &nb= sp;}

    

    Теперь рас­ска­жу о раз­ных вз= гля­дах на об­щую тех­ни­ку. Лич­но я не счи­таю хоть один из этих ме­то­дов пра­ви= ль­ным всег­да, им­хо - дей­ст­во­вать на­до по си­ту­ации. А ос­нов­ны­ми па­ра­м= ет­ра­ми, вли­я­ющи­ми на мой ме­тод от­ло­ва ис­к­лю­че­ний яв­ля­ют­ся раз­мер прог= ­рам­мы, сто­имость за­ка­за и вре­мя на соз­да­ние. Для пок­лон­ни­ков кор­по­ра­ти= в­но­го прог­рам­ми­ро­ва­ния хо­чу на­пом­нить - на­пи­са­ние всех воз­мож­ных оши= ­бок в прог­рам­ме вмес­те с ком­мен­та­ри­ями зай­мет вре­ме­ни боль­ше, чем со= з­да­ние все­го ос­таль­но­го ко­да, без ин­тер­фей­са. И боль­шин­с­т­во кли­ен­тов= хо­тят прог­рам­му быс­т­ро и де­ше­во, а не дол­го и до­ро­го, но с под­роб­ны­ми ошиб­ка­ми :). В от­ло­ве оши­бок глав­ное, им­хо, чтоб ин­фор­ма­ция об от= ­лов­лен­ной ошиб­ке бы­ла дос­та­точ­ной для вас, что­бы эту ошиб­ку най­ти и ис­п­ра­в= ить. А на поль­зо­ва­те­ля на­до вы­во­дить толь­ко! то, с чем он мо­жет спра­ви= ть­ся сам - а это очень нем­но­го.

    

    Несколько ос­нов­ных спор­н= ых воп­ро­сов:

    1.Ловить каж­дый тип= ис­к­лю­че­ния, или ло­вить все сра­зу. Как пра­ви­ло, опас­ный код мо­жет вы­ки­нуть не один тип ис­к­лю­че­ний­, а нес­коль­ко. Од­ни лю­ди го­во­рят, что ло­вить= на­до обя­за­тель­но каж­дый тип от­дель­но, дру­гие го­во­рят, что на­до ло­вить= все в од­ном бло­ке, а уж по­том раз­би­рать­ся что про­изош­ло. Им­хо, ес­ли н= и с од­ним ти­пом ис­к­лю­че­ний поль­зо­ва­тель спра­вить­ся не мо­жет - то ло= ­вить на­до все сра­зу. Поль­зо­ва­те­лю все рав­но что слу­чи­лось - ошиб­ка хра= ­ни­мой про­це­ду­ры, пе­ре­пол­не­ние оче­ре­ди или еще ка­кая хрень - ему глав­ное знать, что "Тран­зак­ция не прош­ла. Поп­ро­буй­те еще раз.".

    2.Заключать в try - = catch блок как мож­но мень­ше ко­да или как мож­но боль­ше. Мож­но соз­да­вать один блок try catch для всей фун­к­ции, а мож­но соз­да­вать по бло­ку на к= аж­дую по­тен­ци­аль­но опас­ную строч­ку. В об­щем и це­лом - это при­мер­но то ж= е, что и пре­ды­ду­щий ва­ри­ант. Лич­но я счи­таю что за­со­вы­вать весь код = фун­к­ции в try catch - бред. Хо­тя воз­мож­ны си­ту­ации ког­да это оп­рав­да­но. Но= вот соз­да­вать по бло­ку на каж­дую строч­ку - это очень уж дол­го и нуд­но и = ред­ко име­ет смысл.

    3.Надо ста­вить бло­= ки catch в опас­ных строч­ках фун­к­ций и один catch где-то на са­мом вер­ху п= рог­рам­мы, или ста­вить гло­баль­ные от­лов­щи­ки на каж­дом уров­не или во­об­ще ни­к= ог­да не ста­вить гло­баль­ных catch бло­ков. Это, по­жа­луй­, са­мый спор­ны= й мо­мент в под­хо­дах. В при­ло­же­нии есть при­мер как сде­лать сов­ре­мен­ный гло­= баль­ный пе­рех­ват­чик ис­к­лю­че­ний. Од­на­ко есть ряд лю­дей­, ко­то­рые счи­та­= ют, что та­кой от­лов­щик - сви­де­тель­с­т­во пло­хо­го ко­да, мол хо­ро­ший к= од дол­жен от­лав­ли­вать все ошиб­ки там, где они воз­ни­ка­ют, а не на уров­= не ma­in фун­к­ции прог­рам­мы. Воз­мож­но они и пра­вы, но я не сог­ла­сен. Е= ще один спор­ный мо­мент здесь - один гло­баль­ный пе­рех­ват­чик или нес­коль= ­ко на каж­дом струк­тур­ном уров­не... Ну это очень силь­но за­ви­сит от мас­ш= ­таб­нос­ти прог­рам­мы. Ес­ли прог­рам­ма ис­поль­зу­ет па­ру де­сят­ков биб­ли­отек, = на­пи­сан­ных раз­ны­ми людь­ми и мо­жет за­ни­мать­ся од­нов­ре­мен­но нес­коль­ки­ми ра= з­ны­ми за­да­ча­ми - то нес­коль­ко пе­рех­ват­чи­ков име­ют смысл. Ес­ли прог­рам= ­ма од­но­по­точ­ная и на­пи­са­на од­ним прог­рам­мис­том - то вряд ли, од­но­= го об­ще­го пе­рех­ват­чи­ка дол­ж­но хва­тить.

    

    И на­пос­ле­док - пом­ни­те, ч= то пос­ле от­ло­ва ис­к­лю­че­ния прог­рам­ма час­то ос­та­ет­ся в не­ком про­ме­жу­т= оч­ном сос­то­янии - по­ло­ви­на зап­ро­шен­ных поль­зо­ва­те­лем дей­ст­вий вы­по= л­не­на, вто­рая нет. Так что ра­бо­тать на­до ос­то­рож­но, что­бы не вой­ти в бес­= ко­неч­ный цикл оши­бок. Осо­бен­но это ка­са­ет­ся еди­но­го пе­рех­ват­чи­ка. Ес­ли = прог­рам­ма прос­тая, то в ко­де пе­рех­ват­чи­ка дол­ж­но быть при­ве­де­ние прог­рам­= мы к ра­бо­че­му сос­то­янию. Ес­ли прог­рам­ма боль­шая и слож­ная - то вез­де,= где по­яв­ле­ние ис­к­лю­че­ния гро­зит на­ру­ше­ни­ем хо­да прог­рам­мы дол­жен сто­ять свой ло­вец.

    

Гла­ва 4. Муль­ти­по­точ­ность.

  &nb= sp; 

    Мульти-поточность (mul­ti-thre= ­ading) - свой­ст­во прог­рамм вы­пол­нять нес­коль­ко за­дач од­нов­ре­мен­но. Ус­= лов­но од­нов­ре­мен­но, ко­неч­но. В за­ви­си­мос­ти от ти­па про­цес­со­ра, од­н= ов­ре­мен­ность мо­жет быть ис­тин­ной и ими­ти­ру­емой. Впро­чем, для боль­шин­с­т­ва прог= ­рам­мис­тов эта раз­ни­ца не­су­щес­т­вен­на.

    Основными мо­мен­та­ми, зат­ру= д­ня­ющи­ми раз­ра­бот­ку муль­ти­по­точ­ных при­ло­же­ний яв­ля­ет­ся не­воз­мож­ность пред­с­ка­зать пос­ле­до­ва­тель­ность за­вер­ше­ния от­дель­ных по­то­ков = на раз­ных ма­ши­нах. Да и от­лад­ка муль­ти­по­точ­ных при­ло­же­ний всег­да = бы­ла го­лов­ной болью. Впро­чем, на VS 2005 вро­де бы с этим поп­ро­ще... по­ка = у ме­ня все вро­де ра­бо­та­ло нор­маль­но. А вот 2003 VS глю­чил страш­но ес­ли ра= з­ные по­то­ки за­пус­ка­лись из раз­ных биб­ли­отек.

    

    При муль­ти­по­точ­ном вы­пол­= не­нии ко­да ос­нов­ной проб­ле­мой яв­ля­ет­ся от­с­ле­жи­ва­ние дос­ту­пов из ра= з­ных по­то­ков к од­ной стра­ни­це па­мя­ти. Ни в ко­ем слу­чае не дол­ж­но по­л= у­чить­ся так, что­бы один по­ток за­пи­сы­вал в пе­ре­мен­ную, ког­да дру­гой счи­ты= ­ва­ет из нее. След­с­т­вие из вы­шес­ка­зан­но­го - ес­ли у вас все ра­бо­та­ет н= ор­маль­но, т.е. по­то­ки не пе­ре­се­ка­ют­ся в од­ной пе­ре­мен­ной­, это не зна­чит,= что они не пе­ре­се­кут­ся на дру­гой ма­ши­не. От­сю­да дру­гая проб­ле­ма - с= ин­х­ро­ни­за­ция по­то­ков. Для ре­ше­ния этих проб­лем бы­ла раз­ра­бо­та­на тех­ни­ка се­м= а­фо­ров и раз­лич­ных син­х­ро­ни­за­то­ров, ко­то­рая, в не­ко­то­рой сте­пе­ни ос= ­та­лась и в .net, од­на­ко те­перь прог­рам­мист прак­ти­чес­ки не име­ет с ней де­= ла.

    

    Кратко о се­ма­фо­рах и син= ­х­ро­ни­за­ции.

    Синхронизация - ме­ха­низм, по= з­во­ля­ющий по­то­кам кон­т­ро­ли­ро­вать соб­с­т­вен­ное вы­пол­не­ние от­но­си­тель­но друг дру­га. В об­щем и це­лом, син­х­ро­ни­за­ция ве­дет­ся че­рез фла­го­= вые пе­ре­мен­ные и сход­на с об­щим ис­поль­зо­ва­ни­ем се­ма­фо­ров. По по­во= ­ду се­ма­фо­ров... ду­мал как луч­ше на­пи­сать, и ре­шил что кар­тин­ка бу­дет наг­ляд­нее:

    

  &nb= sp; слева и спра­ва - по­то­ки, по ним пол­зет про­цесс вы­чис­ле­ния :). По цен­т­ру= - пе­ре­мен­ная с се­ма­фо­ром. Ког­да ле­вый по­ток за­кан­чи­ва­ет счи­тать= - он за­пи­сы­ва­ет дан­ные в пе­ре­мен­ную, по­том пра­вый по­ток при­хо­дит= и счи­ты­ва­ет ее. Вот для то­го, что­бы пра­вый по­ток не счи­тал пе­ре­мен­= ную, по­ка ле­вый ее не за­пи­шет и нуж­на син­х­ро­ни­за­ция... где-то еще дол­= ж­на быть фла­го­вая (bo­ol) пе­ре­мен­ная, со сво­им се­ма­фо­ром, ко­то­рая бу= ­дет со­об­щать, за­кон­чил ли ле­вый по­ток счи­тать.

    

    Когда нуж­на муль­ти­по­точ= ­ность

    Самое рас­п­рос­т­ра­нен­ное и= с­поль­зо­ва­ние муль­ти­по­точ­нос­ти - кноп­ка от­ме­на (или прер­вать) во вре­мя ка­ко­го= -ли­бо про­цес­са - заг­руз­ки/сче­та/рен­де­ра и пр. Воб­щем-то, мож­но раз­де­ли= ть ис­поль­зо­ва­ние мно­гих по­то­ков на две си­ту­ации - прос­тую и слож­ную= :). Прос­тая - один по­ток счи­та­ет (или еще че­го де­ла­ет), вто­рой пе­ре­ри= ­со­вы­ва­ет фор­му, чтоб не ста­ла бе­лой­, про­ве­ря­ет не на­жа­та ли кноп­ка от­ме­н= а и ри­су­ет ин­ди­ка­тор прог­рес­са. Слож­ная - один по­ток на ин­тер­фей­се,= а еще нес­коль­ко за­ня­ты сво­ими де­ла­ми... нап­ри­мер MS Word - один по­т= ок на ин­тер­фей­се, дру­гой на про­вер­ке вво­ди­мо­го, еще один на про­вер­к= е об­нов­ле­ний­, еще один на про­вер­ке ор­фог­ра­фии, еще один на про­вер­ке грам­ма­ти­ки.= Ор­фог­ра­фия и грам­ма­ти­ка син­х­ро­ни­зи­ро­ва­ны меж­ду со­бой и все по­то­ки син­х­= ро­ни­зи­ро­ва­ны с ин­тер­фей­сным.

    

    Мультипоточность в .NET=

    Те из вас, кто чи­тал msdn спр= ав­ку по лю­бым клас­сам MS .NET на­вер­ня­ка ви­де­ли, что для боль­шин­с­т­ва к= лас­сов на­пи­са­но "thre­ad-sa­fe" - т.е. бе­зо­па­сен для муль­ти­по­то= ч­нос­ти. Ина­че го­во­ря, класс име­ет свой, встро­ен­ный се­ма­фор. На­до от­ме­тит= ь, что в .NET все клас­сы име­ют свои се­ма­фо­ры, кро­ме тех, ко­то­рые ис­по= ль­зу­ют un­sa­fe код. В .NET 2.0 си­ту­ация нес­коль­ко из­ме­ни­лась, по срав­не­н= ию с .NET 1.1, в час­т­нос­ти, те­перь по­ми­мо се­ма­фо­ра, каж­дый объ­ект кла= с­са Con­t­rol (и все нас­лед­ни­ки) име­ет при­пис­ку к по­то­ку, в ко­то­ром он соз­дан, и об­ра­тить­ся к не­му из дру­го­го по­то­ка мож­но толь­ко че­ре= з ме­ха­низм De­le­ga­te In­vo­ke. Это поз­во­ля­ет лег­ко от­сечь по­тен­ци­аль­но опас= ­ные об­ра­ще­ния на ста­дии пер­вич­ной от­лад­ки. Ес­ли вы аб­со­лют­но уве­ре= ­ны, что по­то­ки не пе­ре­се­куть­ся, вы мо­же­те от­к­лю­чить этот ме­ха­низм = - ус­та­но­ви­те свой­ст­во Chec­k­Fo­rIl­le­gal­C­ros­sTh­re­ad­Cal­ls объ­ек­та Con­t­rol = рав­ным fal­se.

    

    Обращение к кон­т­ро­лу, соз­д= ан­но­му в дру­гом по­то­ке те­перь выг­ля­дит так:

    

   &nb= sp;public class Form1 : Form

   &nb= sp;{

   &nb= sp;delegate vo­id Set­Tex­t­Cal­lback(string text);

   &nb= sp;private Thre­ad de­moT­h­re­ad =3D null;

   &nb= sp;private Tex­t­Box tex­t­Box1;

   &nb= sp;private vo­id Set­Tex­t­Ma­in() {

   &nb= sp;this.textBox1.Text =3D "This text was set un­sa­fely."; // не­бе­зо­пас­ная ус­та­но= в­ка тек­с­та

   &nb= sp;this.SetText(("This text was set sa­fely."); // бе­зо­пас­ная ус­та­нов­ка

   &nb= sp;}

   &nb= sp;private vo­id Set­Text(string text) {

   &nb= sp;if (this.tex­t­Box1.Invo­ke­Re­qu­ired) { // ес­ли кон­т­рол в дру­гом по­то­к= е

   &nb= sp;SetTextCallback d =3D new Set­Tex­t­Cal­lback(Set­Text); // соз­дать вы­зов

   &nb= sp;this.Invoke(d, new obj­ect[] { text }); // выз­вать фун­к­цию из дру­го­го по­то­ка

   &nb= sp;}

   &nb= sp;else { // ес­ли кон­т­рол в этом по­то­ке

   &nb= sp;this.textBox1.Text =3D text; // ус­та­но­вить текст

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

   &nb= sp;

    Создание прос­той муль­ти­п= о­точ­нос­ти

    Для соз­да­ния прос­той и бе­з= о­пас­ной муль­ти­по­точ­нос­ти в .NET 2.0 луч­ше все­го вос­поль­зо­вать­ся ком­по­н= ен­том Bac­k­g­ro­un­d­Wor­ker (фо­но­вый ра­бот­ник). Очень удоб­ная вещь - ком­п= о­нент, ко­то­рый уме­ет вы­пол­нять при­пи­сан­ную к не­му фун­к­цию в фо­но­вом р= е­жи­ме, с под­дер­ж­кой "отме­ны" и прог­рес­са. Для всех фун­к­ций­, ко­= то­рые не свя­за­ны с ин­тер­фей­сом и дол­ж­ны вы­пол­нять­ся фо­но­во - ре­ко­ме= н­дую поль­зо­вать­ся ра­бот­ни­ком.

    Пример ис­поль­зо­ва­ния в при= ­ло­же­нии.

    Если вам на­до за­пус­тить нес= ­коль­ко по­то­ков од­нов­ре­мен­но, но все они дол­ж­ны вы­пол­нять­ся фо­но­во - и= с­поль­зуй­те его же. Нап­ри­мер, ес­ли вам на­до ска­чи­вать ку­чу фай­лов из се­ти, вы = мо­же­те соз­дать с де­ся­ток ра­бот­ни­ков и пусть все они ка­ча­ют од­нов­ре­мен­н= о. Они все син­х­ро­ни­зи­ру­ют­ся, че­рез прог­ресс, с ос­нов­ным по­то­ком, = но ни­как не свя­за­ны меж­ду со­бой.

    

    Сложная муль­ти­по­точ­ност= ь

    Пару слов о слож­ной муль­ти­п= о­точ­нос­ти. Для на­ча­ла - а вы уве­ре­ны что оно вам на­до? Неп­рос­тое и му­тор­ное э= то де­ло. Но ес­ли вам так уж при­пер­ло за­пус­тить про­гу в ку­чу по­то­ков = од­нов­ре­мен­но, да еще и свя­зан­ных меж­ду со­бой­, то де­ла­ет­ся это при­мер­но так:

    Есть класс Thre­ad (System.Thr= e­ading.Thre­ad). Он обес­пе­чи­ва­ет соб­с­т­вен­но по­то­ки. Есть клас­сы Thre­ad­S­tart и = Pa­ra­me­te­ri­zed­T­h­re­ad­S­tart - они дер­жат ад­рес фун­к­ции ко­то­рую нуж­но за­пус­кать в по­то­ке, пер= ­вый - фун­к­ция дол­ж­на быть без ар­гу­мен­тов, вто­рой - с ар­гу­мен­том ти­па obj­ect.

    

    Синхронизация по­то­ков пол­но= с­тью на вас, рав­но как ес­ли вам прип­рет стал­ки­вать по­то­ки лба­ми в объ­ек= ­те ва­ше­го клас­са - ва­ша за­да­ча от­с­ле­дить, что­бы не бы­ло на­ру­ше­ни= й... Каж­дая пе­ре­мен­ная в .NET thre­ad-sa­fe, но это не оз­на­ча­ет что класс= це­ли­ком то­же thre­ad-sa­fe. Пред­с­тавь­те та­кую си­ту­ацию - вы за­пи­сы­ва­ете в класс пос­ле­до­ва­тель­но зна­че­ния пе­ре­мен­ных в од­ном по­то­ке, а др= у­гой по­ток в это вре­мя их счи­ты­ва­ет. У вас да­же Ex­cep­ti­on не бу­дет - п= рос­то прог­рам­ма нач­нет глю­чить - по­ло­ви­на дан­ных из ста­ро­го сос­то­яния, вто­рая по­ло­ви­на из но­во­го... Что­бы это­го не слу­чи­лось - воз­в­ра­= ща­ем­ся к ис­то­кам, де­ла­ем свои се­ма­фо­ры на каж­дый класс. Есть ку­ча раз­ных= ме­то­дов, я по­ка­жу на очень прос­том при­ме­ре очень прос­той ме­тод.

    

    Создаем класс с се­ма­фо­ром (= за­моч­ком):

    

   &nb= sp;class Cal­c­Res

   &nb= sp;{

   &nb= sp;public bo­ol is­Loc­ked; // флаг за­пер­тос­ти

   &nb= sp;private int loc­kId; // ID по­то­ка ко­то­рый за­пер

   &nb= sp;private int _res; // пе­ре­мен­ная

   &nb= sp;public int res { // свой­ст­во для пе­ре­мен­ной

   &nb= sp;get {

   &nb= sp;if (Thre­ad.Cur­ren­t­T­h­re­ad.Ma­na­ged­T­h­re­adId =3D=3D loc­kId || loc­kI= d =3D=3D 0) { // ес­ли за­мок не ус­та­нов­лен или ус­та­нов­лен этим по­то­ком

   &nb= sp;return _res; // вер­нуть ре­зуль­тат

   &nb= sp;}

   &nb= sp;return 0; // или вер­нуть 0

   &nb= sp;}

   &nb= sp;set {

   &nb= sp;if (Thre­ad.Cur­ren­t­T­h­re­ad.Ma­na­ged­T­h­re­adId =3D=3D loc­kId || loc­kI= d =3D=3D 0) { // ес­ли мож­но

   &nb= sp;_res =3D va­lue; // ус­та­но­вить зна­че­ние

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

   &nb= sp;public Cal­c­Res() { // кон­с­т­рук­тор

   &nb= sp;isLocked =3D fal­se; // не за­перт

   &nb= sp;}

   &nb= sp;public vo­id Lock() { // фун­к­ция За­пе­реть

   &nb= sp;isLocked =3D true; // за­перт

   &nb= sp;lockId =3D Thre­ad.Cur­ren­t­T­h­re­ad.Ma­na­ged­T­h­re­adId; // ус­та­но­вить ID = по­то­ка

   &nb= sp;}

   &nb= sp;public vo­id Un­Lock() { // от­пе­реть

   &nb= sp;if (Thre­ad.Cur­ren­t­T­h­re­ad.Ma­na­ged­T­h­re­adId =3D=3D loc­kId) { // ес­= ли id по­то­ков сов­па­да­ют

   &nb= sp;lockId =3D 0; // снять id

   &nb= sp;isLocked =3D fal­se; // снять за­мок

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

    Простейшая им­п­ле­мен­та­ция = клас­са с воз­мож­нос­тью за­пи­ра­ния на один по­ток. Те­перь по­тес­тим то, что у= нас по­лу­чи­лось:

    Такая вот фор­ма:

    

  &nb= sp; с та­ким вот ко­дом:

    

   &nb= sp;public par­ti­al class Form1 : Form

   &nb= sp;{

   &nb= sp;Thread thre­ad1; // по­ток 1

   &nb= sp;Thread thre­ad2; // по­ток 2

   &nb= sp;CalcRes cal­c­Res; // класс для хра­не­ния ре­зуль­та­тов

   &nb= sp;public Form1() {

   &nb= sp;InitializeComponent();

   &nb= sp;}

   &nb= sp;delegate vo­id Set­Tex­t­Cal­lback(Tex­t­Box textb, string text); // де­ле­гат для з= а­пи­си в тек­с­то­вые по­ля

   &nb= sp;private vo­id but­ton1_Click(obj­ect sen­der, Even­tArgs e) { // на­жа­тие кноп­ки<= o:p>

   &nb= sp;if (but­ton1.Text =3D=3D "but­ton1") { // ес­ли кноп­ку на­жа­ли пер= ­вый раз

   &nb= sp;calcRes =3D new Cal­c­Res(); // соз­дать объ­ект клас­са

   &nb= sp;thread1 =3D new Thre­ad(new Pa­ra­me­te­ri­zed­T­h­re­ad­S­tart(Cal­c­Func)); // со= з­дать по­ток с при­пис­кой к фун­к­ции с па­ра­мет­ром

   &nb= sp;thread2 =3D new Thre­ad(new Thre­ad­S­tart(Sta­te­Func)); // соз­дать по­ток без па= ­ра­мет­ров

   &nb= sp;thread1.Start(1000000); // за­пус­тить по­ток 1 с ар­гу­мен­том 1000000

   &nb= sp;thread2.Start(); // за­пус­тить вто­рой по­ток

   &nb= sp;button1.Text =3D "stop"; // из­ме­нить текст на кноп­ке<= /p>

   &nb= sp;}

   &nb= sp;else { // ес­ли на­жа­ли вто­рой раз

   &nb= sp;thread1.Abort(); // прер­вать по­ток 1

   &nb= sp;thread2.Abort(); // прер­вать по­ток 2

   &nb= sp;button1.Text =3D "but­ton1"; // вос­ста­но­вить текст на кноп­ке

   &nb= sp;}

   &nb= sp;}

   &nb= sp;void Cal­c­Func(obj­ect da­ta) { // фун­к­ция по­то­ка 1

   &nb= sp;for (int i =3D 0; i ‹ (int)da­ta; i++) { // цикл по ар­гу­мен­ту

   &nb= sp;for (int j =3D 0; j ‹ 100; j++) { // цикл на сот­ню - для наг­ляд­нос­ти

   &nb= sp;if (cal­c­Res.isLoc­ked =3D=3D fal­se) { // ес­ли объ­ект не за­перт

   &nb= sp;calcRes.Lock(); // за­пе­реть

   &nb= sp;calcRes.res++; // до­пи­сать зна­че­ние

   &nb= sp;SetText(textBox1, cal­c­Res.res.ToS­t­ring()); // ус­та­но­вить текст

   &nb= sp;calcRes.UnLock(); // от­пе­реть

   &nb= sp;}

   &nb= sp;Thread.Sleep(1); // по­дож­дать мил­ли­се­кун­ду

   &nb= sp;}

   &nb= sp;Thread.Sleep(1000); // по­дож­дать се­кун­ду

   &nb= sp;}

   &nb= sp;}

   &nb= sp;void Sta­te­Func() { // фун­к­ция по­то­ка 2

   &nb= sp;while (thre­ad1.IsA­li­ve) { // ес­ли пер­вая нить еще вы­пол­ня­ет­ся=

   &nb= sp;if (cal­c­Res.isLoc­ked =3D=3D fal­se) { // ес­ли объ­ект не за­перт

   &nb= sp;calcRes.Lock(); // за­пе­реть

   &nb= sp;SetText(textBox2, cal­c­Res.res.ToS­t­ring()); // ус­та­но­вить текст

   &nb= sp;calcRes.UnLock(); // от­пе­реть

   &nb= sp;}

   &nb= sp;else { // ес­ли за­перт

   &nb= sp;SetText(textBox2, "loc­ked"); // на­пи­сать за­пер­то

   &nb= sp;}

   &nb= sp;SetText(textBox3, thre­ad1.Thre­ad­S­ta­te.ToS­t­ring()); // на­пи­сать сос­то­яние по­то­ка = 1

   &nb= sp;Thread.Sleep(10); // по­дож­дать 10 мил­ли­се­кунд

   &nb= sp;}

   &nb= sp;}

   &nb= sp;void Set­Text(Tex­t­Box textb, string text) { // ус­та­нов­ка тек­с­та thre­ad-s= a­fe

   &nb= sp;if (tex­tb.Invo­ke­Re­qu­ired) {

   &nb= sp;SetTextCallback d =3D new Set­Tex­t­Cal­lback(Set­Text);

   &nb= sp;this.Invoke(d, new obj­ect[] { textb, text });

   &nb= sp;}

   &nb= sp;else {

   &nb= sp;textb.Text =3D text;

   &nb= sp;}

   &nb= sp;}

   &nb= sp;private vo­id Form1_For­m­C­lo­sing(obj­ect sen­der, For­m­C­lo­sin­gE­ven­tArgs e)= { // при зак­ры­тии фор­мы

   &nb= sp;if (thre­ad1.IsA­li­ve) thre­ad1.Abort(); // убить по­ток 1<= /i>

   &nb= sp;if (thre­ad2.IsA­li­ve) thre­ad2.Abort(); // убить по­ток 2<= /i>

   &nb= sp;}

   &nb= sp;}

    Вообще-то еще на­до про­ве­рят= ь соз­да­ны ли по­то­ки - при зак­ры­тии и при об­ра­ще­нии из дру­го­го по­то­ка. Но е= с­ли на­до - са­ми до­ба­ви­те. Все ожи­да­ния - для ими­та­ции дол­гой и нап­ря= ­жен­ной ра­бо­ты :). Итак, ес­ли бы по­то­ки ни­ког­да не стал­ки­ва­лись и наш за­= мок не ра­бо­тал, то над­пись loc­ked ни­ког­да бы не по­яви­лась во вто­ром те= к­с­то­вом по­ле, а во вре­мя се­кун­д­ных па­уз пер­во­го по­то­ка в пер­вом тек­с­то= ­вом по­ле всег­да бы бы­ло чис­ло крат­ное 100. На кар­тин­ке по­ка­за­но сос­т= о­яние во вре­мя се­кун­д­ной па­узы.

    

Глава 5. Вызов функций библиотек Win32 (P/Invoke)

  &nb= sp; 

    Вызов фун­к­ций Win32 биб­л= и­отек (p/invo­ke)

    

    Довольно час­тая си­ту­ация - = вам нуж­на фун­к­ция из биб­ли­оте­ки, а она на­пи­са­на для Win32 и в .NET нап= ­ря­мую не под­к­лю­ча­ет­ся. Еще ча­ще - нуж­на сис­тем­ная фун­к­ция, а они все "спря­та­ны" в сис­тем­ных биб­ли­оте­ках, ко­то­рые все Win32. И= нес­мот­ря на мно­го­чис­лен­ные обе­ща­ния Mic­ro­soft, сис­тем­ные биб­ли­оте­ки Vis= ­ta бу­дут то­же Win32. WinFX как был так и ос­та­ет­ся про­ек­том и меч­той.

    

    В язы­ках прог­рам­ми­ро­ва­ни= я до .NET бы­ли раз­лич­ные спо­со­бы им­пор­та фун­к­ций из dll. В .NET свой сп= о­соб, во мно­гом по­хо­жий на то, что бы­ло в с++ 6.0. Есть та­кая вот кон­с­т­ру= к­ция:

    

   &nb= sp;[DllImport(‹имя_dll›, Ат­три­бу­ты)]

которая хранится в namespace System.Runtime.InteropSer= vices. Аттрибуты - необязательны, имя dll - путь к dll, в кавычках.

    

    Аттрибуты вы­зо­ва есть сле­ду= ­ющие:

    CharSet - оп­ре­де­ля­е= т как пе­ре­да­вать стро­ки (ANSI или Uni­co­de). Воз­мож­ные зна­че­ния An­si, A= uto, No­ne, Uni­co­de. Ре­ко­мен­ду­ет­ся ста­вить Auto. Зна­че­ние это­го ат­тр= и­бу­та вли­я­ет на по­иск фун­к­ций в сис­тем­ных биб­ли­оте­ках. Как вы зна­ете, у Mic­ro­soft'a стан­дарт - ес­ли фун­к­ция на­пи­са­на для An­si - в кон­це = име­ни до­пи­сы­ва­ет­ся заг­лав­ная бук­ва А, ес­ли для uni­co­de - W. В ре­жи­ме Auto - ес­ли фун­к­ция с ука­зан­ным име­нем не най­де­на, сис­те­ма бу­дет= ис­кать фун­к­ции с тем же име­нем, но с суф­фик­сом ко­ди­ров­ки. Для Win­dows95 - ищут­ся толь­ко фун­к­ции A, для всех ос­таль­ных - W. Что­бы сис­те­ма не = ис­ка­ла фун­к­ции - ука­жи­те ат­три­бут Exac­t­S­pel­ling=3Dtrue. Ес­ли Char­Set н= е ука­зан, в С# ста­вит­ся зна­че­ние по умол­ча­нию - An­si.

    ExactSpelling - по­ка­з= ы­ва­ет на­до ли ис­кать фун­к­ции с суф­фик­сом ко­ди­ров­ки. По умол­ча­нию =3D f= al­se, т.е. ис­кать на­до.

    EntryPoint - наз­ва­ние= или ад­рес фун­к­ции. Мож­но не за­да­вать, тог­да сис­те­ма бу­дет ис­кать фун= ­к­цию са­ма. Ис­поль­зо­вать име­ет смысл ког­да вам на­до пе­ре­име­но­вать фун­= к­цию. Ну, нап­ри­мер фун­к­ция в биб­ли­оте­ке на­зы­ва­ет­ся "Fun­c­ti­onO­= fAp­plyin­gAt­tri­bu­tes­To­Gi­ve­nO­bj­ec­t­s­Ver­si­on2Mo­del15", а вам та­кое длин­ное имя не нра­вит­ся, и вы хо­ти­те об­ра­щать­ся к фун­= к­ции Ap­plyAt­tri­bu­tes. Вот тог­да вы в En­t­r­y­Po­int пи­ше­те длин­ное имя,= а свою фун­к­цию на­зы­ва­ете так, как вам на­до.

    CallingConvention - по­= ка­зы­ва­ет, как на­до об­ра­щать­ся с па­мятью при вы­зо­ве. Мо­жет при­ни­мать зна­че­= ния - Cdecl, StdCall, This­Call, Wi­na­pi. Зна­че­ние по умол­ча­нию - Wi­na­pi= , ко­то­рое рав­но StdCall на Win­dows ОС, и Cdecl на Win­dows CE. Cdecl ис­поль­зу­ет­= ся для вы­зо­ва фун­к­ций с пе­ре­мен­ным чис­лом ар­гу­мен­тов. This­Call ис­= поль­зу­ет­ся для вы­зо­ва фун­к­ций­, ра­бо­та­ющих с клас­са­ми, ко­то­рые то­же выз­ва= ­ны из Win32 dll.

    Остальные ат­три­бу­ты нуж­ны = очень ред­ко. Под­роб­нее мож­но по­чи­тать здесь - http://msdn2.microsoft.com/en-US/libra= ry/system.runtime.interopservices.dllimportattribute(VS.80).aspx.

    

    Самое слож­ное в вы­зо­ве фун­= к­ций из dll - пре­об­ра­зо­ва­ние ти­пов дан­ных. Ес­ли кто вдруг не зна­ет - до .NET бы­ли дру­гие мас­си­вы, не бы­ло спис­ков и пе­ре­чис­ле­ний­, а еще = поч­ти все пе­ре­да­ва­лось че­рез ука­за­тель... так что рас­смот­рим как ка­кие = ти­пы со­от­но­сят­ся.

    

    Простые ти­пы как пра­в= и­ло так и пи­шут­ся: int, uint, byte.

    Таблица со­от­вет­с­т­вий прос= ­тых ти­пов тут - http://msdn= 2.microsoft.com/en-us/library/ac7ay120.aspx.

    

    Строки. Под­роб­нее зде= сь - h= ttp://msdn2.microsoft.com/en-us/library/e8w969hb.aspx.

    Со стро­ка­ми де­ло нес­коль­к= о ху­же. Они пе­ре­да­ют­ся по раз­но­му, в за­ви­си­мос­ти от си­ту­ации:

    Если стро­ка вхо­дя­щий па­ра­= метр - пе­ре­да­ет­ся как string, ес­ли фун­к­ция под­дер­жи­ва­ет Uni­co­de.

    

   &nb= sp;[DllImport("User32.dll", En­t­r­y­Po­int=3D"Mes­sa­ge­Box", Char­Set=3DChar­Set.Auto)]

   &nb= sp;public sta­tic ex­tern int MsgBox(int hWnd, String text, String cap­ti­on, uint ty= pe);

    

 Если строка возвращаемый параметр - тоже просто string.

    Если стро­ка пе­ре­да­ет­ся ка= к ука­за­тель, вхо­дя­щий па­ра­метр - Strin­g­Bu­il­der.

    

   &nb= sp;[DllImport( "Ker­nel32.dll", Char­Set=3DChar­Set.Auto)]=

   &nb= sp;public sta­tic ex­tern int Get­S­y­s­tem­Di­rec­tory(Strin­g­Bu­il­der sysDir­Buf­= fer, int si­ze);

    Если стро­ка воз­в­ра­ща­ет­ся= как ука­за­тель - дек­ла­ри­ру­ет­ся как ука­за­тель, ко­то­рый по­том пе­ре­во= ­дит­ся в стро­ку.

    

   &nb= sp;[ DllIm­port( "Ker­nel32.dll", Char­Set=3DChar­Set.Auto )] pub­lic = sta­tic ex­tern IntPtr Get­Com­man­d­Li­ne();

   &nb= sp;// ис­поль­зо­ва­ние

   &nb= sp;IntPtr cmdLi­neStr =3D Get­Com­man­d­Li­ne();

   &nb= sp;String com­man­d­Li­ne =3D Mar­s­hal.PtrToS­t­rin­gA­uto( cmdLi­neStr );

    

    Массивы. Под­роб­нее зд= есь - h= ttp://msdn2.microsoft.com/en-us/library/dd93y453.aspx.

    Массивы пе­ре­да­ют­ся нап­ря­= мую. Вы ука­зы­ва­ете мо­ди­фи­ка­то­ры In, Out для ука­за­ния что имен­но мож­н= о с мас­си­вом де­лать - чи­тать (In) и/или пи­сать (Out).

    

   &nb= sp;// фун­к­ция: int Tes­tAr­ra­yO­fIn­ts(int* pAr­ray, int pSi­ze);

   &nb= sp;[ DllIm­port( "..\\LIB\\Pin­vo­ke­Lib.dll" )]=

   &nb= sp;public sta­tic ex­tern int Tes­tAr­ra­yO­fIn­ts([In, Out] int[] ar­ray, int si­ze = );

    

    Если вам ну­жен ука­за­тель на= мас­сив, нес­коль­ко слож­нее.

    

   &nb= sp;//функция: int Tes­t­Re­fAr­ra­yO­fIn­ts(int** ppAr­ray, int* pSi­ze);

   &nb= sp;[ DllIm­port( "..\\LIB\\Pin­vo­ke­Lib.dll" )]=

   &nb= sp;public sta­tic ex­tern int Tes­t­Re­fAr­ra­yO­fInts( ref IntPtr ar­ray, ref int si= ­ze );

   &nb= sp;// ис­поль­зо­ва­ние

   &nb= sp;int[] ar­ray2 =3D new int[10];

   &nb= sp;int si­ze =3D ar­ray2.Length;

   &nb= sp;IntPtr buf­fer =3D Mar­s­hal.Alloc­Co­Tas­k­Mem( Mar­s­hal.Si­ze­Of(si­ze) *array2.Length);

   &nb= sp;Marshal.Copy( ar­ray2, 0, buf­fer, ar­ray2.Length );

   &nb= sp;int sum2 =3D Tes­t­Re­fAr­ra­yO­fInts( ref buf­fer, ref si­ze );

    

    Если вам на­до пе­ре­дать мас­= сив строк, т.е. ука­за­тель на мас­сив char, мож­но сде­лать так:

    

   &nb= sp;// фун­к­ция: int Tes­tAr­ra­yOf­S­t­rin­gs(char** ppStrAr­ray, int si­ze);

   &nb= sp;[ DllIm­port( "..\\LIB\\Pin­vo­ke­Lib.dll" )]

   &nb= sp;public sta­tic ex­tern int Tes­tAr­ra­yOf­S­t­rings( [In, Out] String[] strin­gAr­= ray, int si­ze );

    

    Если мас­сив пос­то­ян­ный (co= nst), то его мож­но пе­ре­дать так:

    

   &nb= sp;// па­ра­метр с++: TCHAR szCSDVer­si­on[ 128 ];

   &nb= sp;[ Mar­s­ha­lAs( Un­ma­na­ged­T­y­pe.ByValTStr, Si­ze­Const=3D128 )] pub­lic String ver­si­o= n­S­t­ring;

   &nb= sp;// па­ра­метр с++: int val[3];

   &nb= sp;[ Mar­s­ha­lAs( Un­ma­na­ged­T­y­pe.ByVa­lAr­ray, Si­ze­Const=3D3 )] pub­lic int[] val;

    Конструкцию Mar­s­ha­lAs рас­с= мот­рим ни­же.

    

    Структуры и клас­сы. По= д­роб­нее здесь - http://msdn2.microsoft.com/en-us/library/eshywdt7.aspx.

    Часто не­об­хо­ди­мо пе­ре­да­= вать струк­ту­ры как ар­гу­мен­ты, в этом слу­чае дей­ст­ву­ем сле­ду­ющим об­ра= ­зом:

    

   &nb= sp;//структура с:

   &nb= sp;typedef struct _MYPER­SON

   &nb= sp;{

   &nb= sp;char* first;

   &nb= sp;char* last; } MYPER­SON, *LP_MYPER­SON;

   &nb= sp;// соз­да­ем свою струк­ту­ру

   &nb= sp;[ Struc­t­La­yo­ut( La­yo­ut­Kind.Se­qu­en­ti­al, Char­Set=3DChar­Set.Ansi )]=

   &nb= sp;public struct MyPer­son {

   &nb= sp;public String first;

   &nb= sp;public String last;

   &nb= sp;}

    Передаем струк­ту­ру как обыч­= ный ар­гу­мент, ли­бо нап­ря­мую, ли­бо ука­за­тель на нее ис­поль­зуя сло­во ref. Па­ра­ме= тр Char­Set в кон­с­т­рук­ции Struc­t­La­yo­ut ис­поль­зо­вать на­до так­же, к= ак в кон­с­т­рук­ции DLLIm­port, т.е. ес­ли стро­ко­вые пе­ре­мен­ные есть - луч= ­ше ука­зы­вать ка­кие они имен­но эти стро­ки.

    Классы пе­ре­да­ют­ся так­же к= ак струк­ту­ры. И для каж­дой с++ струк­ту­ры, вы мо­же­те соз­да­вать свой кл= асс.

    

   &nb= sp;//структура с++

   &nb= sp;typedef struct _SYSTEM­TI­ME

   &nb= sp;{

   &nb= sp;WORD wYe­ar;

   &nb= sp;WORD wMonth;

   &nb= sp;WORD wDa­yOf­We­ek;

   &nb= sp;WORD wDay;

   &nb= sp;WORD wHo­ur;

   &nb= sp;WORD wMi­nu­te;

   &nb= sp;WORD wSe­cond;

   &nb= sp;WORD wMil­li­se­conds; } SYSTEM­TI­ME, *PSYSTEM­TI­ME;

   &nb= sp;// наш класс

   &nb= sp;[ Struc­t­La­yo­ut( La­yo­ut­Kind.Se­qu­en­ti­al )]

   &nb= sp;public class System­Ti­me {

   &nb= sp;public us­hort ye­ar; …

   &nb= sp;public us­hort mil­li­se­conds;

   &nb= sp;}

   &nb= sp;// ис­поль­зо­ва­ние

   &nb= sp;SystemTime st =3D new System­Ti­me();

   &nb= sp;GetSystemTime( st );

    

    Еще один мо­мент, ес­ли фун­к­= ция биб­ли­оте­ки ожи­да­ет ука­за­тель на струк­ту­ру и од­ним из ва­ри­ан­тов= мо­жет быть null - соз­да­вай­те класс, а не струк­ту­ру. Ес­ли ар­гу­мент фун­к­ц= ии C# - класс, то null пе­ре­да­вать мож­но, ес­ли струк­ту­ра - нель­зя.

    

    Прочее. Под­роб­нее зде= сь - h= ttp://msdn2.microsoft.com/en-us/library/ss9sb93t.aspx.

    Есть еще не­ко­то­рые ти­пы да= н­ных, ко­то­рые иног­да на­до пе­ре­да­вать и ко­то­рые весь­ма нес­тан­дар­т­но = об­ра­ба­ты­ва­ют­ся.

    Указатель на фун­к­цию<= /p>

    В С# ука­за­тель на фун­к­цию = прев­ра­тил­ся в de­le­ga­te.

    

   &nb= sp;// фун­к­ция с ++

   &nb= sp;void Tes­t­Cal­lBack(FPTR pf, int va­lue);

   &nb= sp;// дек­ла­ра­ция в C#

   &nb= sp;public de­le­ga­te bo­ol FPtr( int va­lue );

   &nb= sp;[ DllIm­port( "..\\LIB\\Pin­vo­ke­Lib.dll" )]=

   &nb= sp;public sta­tic ex­tern vo­id Tes­t­Cal­lBack( FPtr cb, int va­lue );

    

    HandleRef

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

    

   &nb= sp;// фун­к­ция с++

   &nb= sp;bool Re­ad(Han­d­le hFi­le);

   &nb= sp;// фун­к­ция C#

   &nb= sp;[DLLImport("lib.dll")]

   &nb= sp;public sta­tic ex­tern bo­ol Re­ad(Han­d­le­Ref hndRef);

   &nb= sp;// ис­поль­зо­ва­ние

   &nb= sp;FileStream fs =3D new Fi­leS­t­re­am( "Han­d­le­Ref.txt", Fi­le­Mo­de.Open )= ;

   &nb= sp;HandleRef hr =3D new Han­d­le­Ref( fs, fs.Han­d­le );

   &nb= sp;Read(hr);

    

    LPARAM

    Часто встре­ча­ет­ся та­кой во= т ар­гу­мент у сис­тем­ных фун­к­ций. Это ука­за­тель на па­ра­метр. Пе­ре­да­ет­ся та­к= им вот об­ра­зом:

    

   &nb= sp;// фун­к­ция с++

   &nb= sp;BOOL En­su­re(LPA­RAM lPa­ram);

   &nb= sp;// фун­к­ция C#

   &nb= sp;[ DllIm­port( "lib.dll" )]

   &nb= sp;public sta­tic ex­tern bo­ol En­su­re( IntPtr pa­ram );

   &nb= sp;// ис­поль­зо­ва­ние

   &nb= sp;TextWriter tw =3D System.Con­so­le.Out;

   &nb= sp;GCHandle gch =3D GCHan­d­le.Alloc( tw );

   &nb= sp;Ensure( (IntPtr)gch );

   &nb= sp;gch.Free();

    

    void*

    Бывает та­кая вот вещь - ука­з= а­тель на пус­то­ту... Пе­ре­да­ет­ся так:

    

   &nb= sp;// фун­к­ция с++

   &nb= sp;void Set­Da­ta(vo­id* obj­ect);

   &nb= sp;// фун­к­ция C#

   &nb= sp;[ DllIm­port( "lib.dll" )]

   &nb= sp;public sta­tic ex­tern vo­id Set­Da­ta( [Mar­s­ha­lAs(Unma­na­ged­T­y­pe.AsAny)] O= bj­ect o);

   &nb= sp;// ис­поль­зо­ва­ние

   &nb= sp;SetData( (short)12 );

   &nb= sp;SetData( (do­ub­le)12 );

   &nb= sp;SetData( "abcd" );

    

    MarshalAs кон­с­т­рук­ция. Под­роб­нее здесь - http://msdn2.microsoft.com/en-US/library/syst= em.runtime.interopservices.marshalasattribute.aspx.

    Эта кон­с­т­рук­ция поз­во­ля­= ет ука­зать как имен­но дол­жен об­ра­ба­ты­вать­ся объ­ект. За­да­ет­ся так:

    

   &nb= sp;MarshalAs(тип, ат­три­бу­ты)

    Аттрибуты не­обя­за­тель­ны. В= ре­аль­ном ис­поль­зо­ва­нии я ви­дел толь­ко ат­три­бут Si­ze­Const, ука­зы­ва­ющий р= аз­мер пос­то­ян­но­го мас­си­ва.

    Тип, сог­лас­но ко­то­ро­му на= ­до об­ра­ба­ты­вать объ­ект, луч­ше все­го вы­би­рать из пе­ре­чис­ле­ния Un­ma­na­ged­T­y­pe (= http://msdn2.mic­ro­soft.com/en-US/lib­rary/system= .run­ti­me.inte­rop­ser­vi­ces.unma­na­ged­t­y­pe.aspx). Часть из пред­ло­же­ных ти­пов я уже ис­поль­зо­вал в при­ме­рах, про ос­та= ль­ные про­чи­та­ете са­ми - боль­но их мно­го.

    

Часть 3.

  &nb= sp;Некоторые дополнительные осо­бен­нос­ти прог­рам­ми­ро­ва­ния под .net.    =

Глава 1. Set­tings.

  &nb= sp; 

 

  &nb= sp; У каж­дой­, бо­лее или ме­нее боль­шой прог­рам­мы есть ряд нас­т­ро­ек, ко­т= о­рые поль­зо­ва­тель мо­жет ме­нять. Ес­тес­т­вен­но, каж­до­му поль­зо­ва­те­лю= хо­чет­ся, что­бы прог­рам­ма за­по­ми­на­ла его нас­т­рой­ки и ее не на­до бы­ло пе­р= е­нас­т­ра­ивать за­но­во при каж­дом за­пус­ке. У не­ко­то­рых осо­бен­но боль­ших прог­рам= м мо­гут быть соб­с­т­вен­ные нас­т­рой­ки, ко­то­рые ус­та­нав­ли­ва­ют­ся в мо­мен= т ин­с­тал­ля­ции или при пер­вом за­пус­ке, эти нас­т­рой­ки то­же луч­ше за­по­ми­нать, что= ­бы не нас­т­ра­ивать прог­рам­му при каж­дом за­пус­ке.

  &nb= sp; 

    Как за­по­ми­нать нас­т­рой= ­ки

    Стандартное ре­ше­ние - соз­да= ть класс для хра­не­ния па­ра­мет­ров нас­т­ро­ек и выг­ру­жать его со­дер­жи­= мое в файл. Хоть вруч­ную, хоть се­ри­али­за­ци­ей. За­пи­сы­вать все нас­т­рой= ­ки в ре­естр - пло­хая при­выч­ка. Ес­ли на­до хра­нить нас­т­рой­ки для нес­к= оль­ких поль­зо­ва­те­лей - мож­но прос­то файл сох­ра­нять в ка­та­лог поль­зо­ва­= те­ля, или на каж­до­го поль­зо­ва­тел­ся за­во­дить свой файл. На Вин­дах 2000 и = поз­д­нее есть свои ка­та­ло­ги, на ос­таль­ных при­дет­ся сде­лать это вруч­ную. Лич= ­но я счи­таю, что для прог­рамм, не под­дер­жи­ва­ющих мно­го­поль­зо­ва­тель­= ность, это оп­ти­маль­ный ва­ри­ант - сох­ра­нять класс нас­т­ро­ек в файл в род­н= ом ка­та­ло­ге прог­рам­мы. Но тут мо­жет быть проб­ле­ма не­сов­мес­ти­мос­ти= вер­сий­, ко­то­рую по уму на­до всег­да ре­шать, од­на­ко ма­ло кто с этим свя­зы­ва= ­ет­ся. И ус­та­нов­ка но­вой вер­сии прог­рам­мы, как пра­ви­ло, при­во­дит к по­т= е­ре всех нас­т­ро­ек пре­ды­ду­щей вер­сии. Но эта проб­ле­ма лег­ко ре­ша­ет­с= я - файл нас­т­ро­ек дол­жен со­дер­жать в на­ча­ле но­мер вер­сии, от ко­то­ро= й он соз­дан, а прог­рам­ма дол­ж­на счи­ты­вать нас­т­рой­ки в со­от­вет­с­т­ви= и с ука­зан­ной вер­си­ей. Нап­ри­мер, своя фун­к­ция счи­ты­ва­ния для каж­дой= вер­сии фай­ла нас­т­ро­ек.

    

    Решение .NET 2.0 - ис­поль­зо­= вать тех­но­ло­гию Set­tings. К со­жа­ле­нию, при всем удоб­с­т­ве, тех­но­ло­ги= я не до­ра­бо­та­на и не без глю­ков. В об­щем ви­де это выг­ля­дит так - в нас­= т­рой­ках про­ек­та, сре­ди свойств, есть вклад­ка Set­tings. Там вы мо­же­те за­да­в= ать па­ра­мет­ры. Я уже по­ка­зы­вал как это де­лать, но в об­щем ви­де, это пр= и­мер­но так:

    

    Первый стол­бец - имя па­ра­мет­ра, вто­рой - тип, тре­тий - груп­па, чет­вер­тый - зна­че­ние. Тип па­ра­мет­ра мо­жет быть поч­ти лю­бым - прос­тые ти­пы дан= ­ных, вклю­чая string, и спе­ци­аль­ный - кол­лек­ция string, а так­же нес­коль­ко клас­сов ри­со­ва­ния, con­nec­ti­on string, и все что угод­но че­рез ва­ри= ­ант Brow­se. Един­с­т­вен­ное ус­ло­вие - тип дол­жен иметь или ToS­t­ring/From= ­S­t­ring фун­к­ции для Type­Con­ver­ter, или быть xmlSe­ri­ali­zab­le. Груп­па мо­жет быть или User - нас­т­рой­ки сох­ра­ня­ют­ся для каж­до­го поль­зо­ва­те­ля= , и мо­гут быть им из­ме­не­ны, или Ap­pli­ca­ti­on - нас­т­рой­ки не мо­гут бы= ть из­ме­не­ны поль­зо­ва­те­лем. Смысл груп­пы Ap­pli­ca­ti­on - ес­ли нуж­ны= кон­с­тан­ты, ко­то­рые не мо­гут быть за­да­ны прог­рам­мис­том (нап­ри­мер, па­ра­мет­ры ком­па или ти­па то­го) и за­да­ют­ся при ин­с­тал­ля­ции прог­рам­мы, что­= бы не вы­чис­лять па­ра­мет­ры при каж­дом за­пус­ке - они за­пи­сы­ва­ют­ся в= та­кие вот свой­ст­ва. Впро­чем, в эти свой­ст­ва мож­но за­пи­сать и обыч­ные кон= ­с­тан­ты.

    Основные пре­иму­щес­т­ва - Se= t­tings са­ми от­с­ле­жи­ва­ют вер­сию прог­рам­мы и поль­зо­ва­те­ля. Есть встро­е= н­ные фун­к­ции сох­ра­не­ния, заг­руз­ки и воз­в­ра­та к стан­дар­т­ным нас­т­ро= й­кам. Мож­но за­вя­зать кон­т­ро­лы нап­ря­мую на set­tings. Са­ми заг­ру­жа­ют­ся при за­пус­ке, са­ми сох­ра­ня­ют­ся при зак­ры­тии, са­ми за­пол­ня­ют­ся = ес­ли свя­за­ны с кон­т­ро­лом.

    Основные не­дос­тат­ки - на ка= ж­дую вер­сию соз­да­ет­ся свой файл, что из­ряд­но за­со­ря­ет ка­та­лог поль­зо= ­ва­те­ля, ес­ли вер­сии вы­хо­дят дос­та­точ­но час­то. Кста­ти, стан­дар­т­ным де­ин= ­с­тал­ля­то­ром нас­т­рой­ки не уда­ля­ют­ся. Воз­в­рат к стан­дар­т­ным нас­т­рой­кам и со= х­ра­не­ние - иног­да глю­чат. Связ­ка кон­т­ро­лов с па­ра­мет­ром - мо­жет по­рож­дать глю­ки. Все зна­че­ния нас­т­ро­ек сох­ра­ня­ют­ся ли­бо в тек­с­то­вом фор= ­ма­те, ли­бо че­рез xmlSe­ri­ali­zer, что иног­да силь­но уве­ли­чи­ва­ет объ­ем.<= /p>

    

    Общая схе­ма ра­бо­ты с= Set­tings при­мер­но та­кая:

    Если вы не свя­зы­ва­ете кон­т= ­ро­лы с set­tings:

    Задаете свой­ст­ва в ди­зай­не= ­ре, при заг­руз­ке фор­мы - заг­ру­жа­ете set­tings и пе­ре­пи­сы­ва­ете зна­че= ­ния свойств в кон­т­ро­лы, при зак­ры­тии фор­мы - пе­ре­пи­сы­ва­ете зна­че­ни= я из кон­т­ро­лов в set­tings и сох­ра­ня­ете их.

    Это строч­ка об­ра­ще­ния к по= с­лед­ним сох­ра­нен­ным set­tings:

    

   &nb= sp;namespace.Properties.Settings.Default

    Вместо "na­mes­pa­ce"= ; - под­с­тав­те тот na­mes­pa­ce в ко­то­ром вы ра­бо­та­ете.

    Загрузка нас­т­ро­ек, мож­но п= ря­мо за­вес­ти по­ле в фор­ме:

    

   &nb= sp;namespace.Properties.Settings sets =3D na­mes­pa­ce.Pro­per­ti­es.Set­tin­gs.De­fa­ult;=

    

    Переписывание зна­че­ний в кон= ­т­ро­лы:

    

   &nb= sp;textBox1.Text =3D sets.Tex­t­F­rom­Tex­t­box1;

    

    Переписывание зна­че­ний из ко= н­т­ро­лов:

    

   &nb= sp;sets.TextFromTextbox1 =3D tex­t­Box1.Text;

    

    Сохранение нас­т­ро­ек:

    

   &nb= sp;sets.Save();

    

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

    Все кон­т­ро­лы дол­ж­ны быть = с им­п­ле­мен­ти­ро­ван­ным IBin­dab­le­Com­po­nent ин­тер­фей­сом. Со­от­вет­с­т­вен­но, ес­ли вы поль= ­зу­етесь сво­им кон­т­ро­лом - ин­тер­фей­с им­п­ле­мен­ти­ро­вать бу­де­те са­ми. Е= с­ли кон­т­рол без ин­тер­фей­са - пе­ре­пи­сы­ва­ние зна­че­ний и заг­руз­ка оп= ять ля­гут на вас.

    Некоторые кон­т­ро­лы из-за ав= ­то­ма­ти­чес­кой син­х­ро­ни­за­ции с set­tings глю­чат, и их все рав­но при­хо­дит­ся об­ра= ­ба­ты­вать вруч­ную.

    

    Для удоб­с­т­ва уп­рав­ле­ния = есть еще 4 со­бы­тия клас­са set­tings:

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

    SettingChanged - сра­ба= ­ты­ва­ет до из­ме­не­ния зна­че­ния свой­ст­ва. Ре­ко­мен­ду­ет­ся для про­вер­ки вв= е­ден­но­го зна­че­ния на пра­виль­ность.

    PropertyChanged - не ре= ­ко­мен­до­ва­но к ис­поль­зо­ва­нию, ес­ли не тре­бу­ет­ся про­вер­ка вве­ден­но­го свой­ст= ­ва в от­дель­ном по­то­ке.

    SettingsSaved - сра­ба­= ты­ва­ет пе­ред за­писью свойств в файл. Ре­ко­мен­ду­ет­ся ис­поль­зо­вать для про­= вер­ки пра­виль­нос­ти свойств пе­ред сох­ра­не­ни­ем, и для до­пол­ни­тель­ной си= н­х­ро­ни­за­ции зна­че­ний.

    

    Итого, set­tings хо­ро­шо ис­п= оль­зо­вать ес­ли у вас нет соб­с­т­вен­ных кон­т­ро­лов, ко­то­рые на­до свя­зы­вать, = ес­ли все кон­т­ро­лы, ко­то­рые вы ис­поль­зу­ете не глю­чат и ес­ли вер­сии ва­= шей прог­рам­мы вы­хо­дят ред­ко. Тог­да это удоб­но, мож­но че­рез ди­зай­нер = за­вя­зать ку­чу па­ра­мет­ров внеш­не­го ви­да в нас­т­рой­ки, и бу­дет у вас кле­вая= про­га, ко­то­рая пом­нит в ка­ком мес­те эк­ра­на ее зак­ры­ли :).

    

    Основное на этом за­кан­чи­ва­= ет­ся. Ос­таль­ное - вся­кие при­ко­лы, а-ля соб­с­т­вен­ные клас­сы для свойств, = для об­ра­бот­чи­ка свойств и пр.

    

Гла­ва 2. Се­ри­али­за­ция.

  &nb= sp; 

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

    

    Пример 1. Пред­по­ло­жим вы пи= ­ше­те прог­рам­му для ра­бо­ты с ди­аг­рам­ма­ми, а-ля Vi­sio. Ито­гом ра­бо­ты п= оль­зо­ва­те­ля бу­дет изоб­ра­же­ние - рас­т­ро­вое, век­тор­ное - не важ­но. Но про­ме­жу= ­точ­ный ре­зуль­тат ра­бо­ты - это имен­но ди­аг­рам­ма, т.е. вся­кие фор­мы, стрел= ­ки, текст, все это име­ет па­ра­мет­ры ти­па ко­ор­ди­нат, цве­та и пр. Поль­зо= ­ва­тель, ра­зу­ме­ет­ся, за­хо­чет сох­ра­нить свою ра­бо­ту, при­чем сох­ра­нить та= к, что­бы мож­но бы­ло по­том ре­дак­ти­ро­вать. Как вы пос­ту­пи­те? Ну, ско­= рее все­го, соз­да­ди­те свой фор­мат фай­ла и бу­де­те ту­да пос­ле­до­ва­тель= ­но выг­ру­жать все, что поль­зо­ва­тель на­соз­да­вал. Вот тут-то вам и по­на­= до­бить­ся се­ри­али­зи­ция - вы мо­же­те выг­ру­жать объ­ек­ты клас­са, вмес­те со вс= е­ми свой­ст­ва­ми ав­то­ма­ти­чес­ки. Со­от­вет­с­т­вен­но, ког­да поль­зо­ва­т= ель за­хо­чет заг­ру­зить ра­бо­ту - вы за­пус­ка­ете де­се­ри­али­за­цию.

    

    Пример 2. Пред­по­ло­жим, у ва= с есть прог­рам­ма, сос­то­ящая из двух час­тей - кли­ен­т­с­кой и сер­вер­ной. По= ль­зо­ва­тель ра­бо­та­ет на кли­ен­те, а по­том сох­ра­ня­ет ра­бо­ту на сер­ве­ре. Вам = на­до пе­ре­дать все, что он на­ра­бо­тал на сер­вер... Как имен­но пе­ре­да­вать= - не суть важ­но, в лю­бом слу­чае, пе­ре­дать вы мо­же­те толь­ко по­ток бай= т, зна­чит сна­ча­ла, на­до при­вес­ти ра­бо­ту поль­зо­ва­те­ля к это­му по­т= о­ку. Се­ри­али­за­ция имен­но для это­го и де­ла­лась - прос­то, на сей раз, вы = выг­ру­жа­ете ра­бо­ту не в файл, а в се­те­вой по­ток. А сер­вер­ная часть, по­лу­чив по= ­ток, де­се­ри­али­зи­ру­ет его и за­пи­сы­ва­ет ку­да ей на­до.

    

    Насчет где ис­поль­зу­ет­ся= мож­но счи­тать я ска­зал. В при­ме­рах вы­ше два ос­нов­ных ис­поль­зо­ва­= ния тех­ни­ки. В об­щем ви­де это зву­чит как "исполь­зу­ет­ся при не­об­х= о­ди­мос­ти пе­ре­дать дан­ные об объ­ек­те клас­са меж­ду дву­мя при­ло­же­ни­ями, уме= ­ющи­ми с этим клас­сом ра­бо­тать". Хо­тя, это не сов­сем вер­но, пос­коль­ку= мож­но и не уметь ра­бо­тать с клас­сом, прос­то тог­да не по­нят­но, за­чем это н= а­до?..

    

    Как поль­зо­вать­ся

    Очень прос­то - лю­бой класс, = ко­то­рый име­ет ат­три­бут [Se­ri­ali­zab­le] и фун­к­цию Ge­tO­bj­ec­t­Da­ta, мо­жет быть се­ри­али­зо­ван. Ес­ли у клас­са есть де­се­ри­али­за­ци­он­ный кон­с= ­т­рук­тор - он мо­жет быть де­се­ри­али­зо­ван. Боль­шин­с­т­во клас­сов .net под­дер= ­жи­ва­ют се­ри­али­за­цию. Об­ра­ти­те вни­ма­ние - ког­да вы смот­ри­те опи­са­ние = клас­са в msdn - у мно­гих клас­сов есть при­пис­ка ISe­ri­ali­zab­le, IX­m­l­Se­ri= ­ali­zab­le, а пе­ред наз­ва­ни­ем клас­са сто­ит [Se­ri­ali­zab­le­At­tri­bu­te] (Нап­р= и­мер, Da­ta­Tab­le). У струк­тур (нап­ри­мер, Po­int) есть толь­ко ат­три­бут. Им= ин­тер­фей­с не ну­жен.

    Как сде­лать свой класс се­ри­= али­зи­ру­емым:

    

   &nb= sp;[Serializable()]

   &nb= sp;[ComVisibleAttribute(false)]

   &nb= sp;public class Se­ri­ali­zab­leC­lass : ISe­ri­ali­zab­le

   &nb= sp;{

   &nb= sp;public Se­ri­ali­zab­leC­lass() {

   &nb= sp;dataField =3D 0;

   &nb= sp;dataField2 =3D 0.0;

   &nb= sp;}

   &nb= sp;private int da­ta­Fi­eld;

   &nb= sp;private do­ub­le da­ta­Fi­eld2;

   &nb= sp;public vir­tu­al vo­id Ge­tO­bj­ec­t­Da­ta(Se­ri­ali­za­ti­onIn­fo in­fo, Stre­ami= n­g­Con­text con­text) {

   &nb= sp;// до­бав­ля­ем пе­ре­мен­ные в спи­сок на сох­ра­не­ние

   &nb= sp;info.AddValue("dataField", da­ta­Fi­eld);

   &nb= sp;info.AddValue("dataField2", da­ta­Fi­eld2);

   &nb= sp;}

   &nb= sp;}

    

    Как вклю­чить де­се­ри­али­за­= цию - до­ба­вить кон­с­т­рук­тор:

    

   &nb= sp;public Se­ri­ali­zab­leC­lass(Se­ri­ali­za­ti­onIn­fo in­fo, Stre­amin­g­Con­text = con­text) : this() {

   &nb= sp;dataField =3D in­fo.Ge­tInt32("da­ta­Fi­eld");

   &nb= sp;dataField2 =3D in­fo.Get­Do­ub­le("da­ta­Fi­eld2");

   &nb= sp;}

    

    А мож­но де­се­ри­али­за­цию с= де­лать уни­вер­саль­ной­, ис­поль­зуя Ref­lec­ti­on:

    

   &nb= sp;public Se­ri­ali­zab­leC­lass(Se­ri­ali­za­ti­onIn­fo in­fo, Stre­amin­g­Con­text = con­text) : this() {

   &nb= sp;SerializationInfoEnumerator en =3D in­fo.Ge­tE­nu­me­ra­tor();

   &nb= sp;en.MoveNext();

   &nb= sp;for (int i =3D 0; i ‹ in­fo.Mem­ber­Co­unt; i++) {

   &nb= sp;this.GetType().InvokeMember(en.Current.Name, Bin­din­g­F­lags.Instan­ce | Bin­din­g­F­lags.Pub­lic | Bin­din­g­F­lags.No= n­Pub­lic | Bin­din­g­F­lags.Set­P­ro­perty, null, this, new obj­ect[1] { en.Cur­rent= .Va­lue });

   &nb= sp;en.MoveNext();

   &nb= sp;}

   &nb= sp;}

    

    Сериализацию то­же мож­но сде­= лать уни­вер­саль­ной­, по су­ти, ес­ли не им­п­ле­мен­ти­ро­вать ин­тер­фей­с -= се­ри­али­за­тор бу­дет ста­рать­ся выг­ру­зить все чле­ны клас­са... Но, как пра­ви­ло, в к= лас­се на­хо­дить­ся нес­коль­ко чле­нов без ат­три­бу­та Se­ri­ali­zab­le, и се­р= и­али­за­тор вы­ки­ды­ва­ет ошиб­ку. По­это­му, ча­ще все­го, при­хо­дить­ся чле­ны на з= а­пись за­би­вать вруч­ную, че­рез Ge­tO­bj­ec­t­Da­ta.

    

    Как за­пус­тить се­ри­али­за­ц= ию:

    Предположим, у нас в клас­се в= ся нуж­ная ин­фо­рам­ция хра­нить­ся в кол­лек­ции Da­ta­Obj­ects, яв­ля­ющей­ся кол­л= ек­ци­ей объ­ек­тов ти­па Se­ri­ali­zab­leC­lass. Так­же, пред­по­ло­жим, у Se­ri­al= i­zab­leC­lass есть event mo­use­Down. Event'ы не сох­ра­ня­ют­ся, ес­тес­т­вен­но, по­это= ­му при де­се­ри­али­за­ции, на­до event опять ус­та­нав­ли­вать.

    

   &nb= sp;internal vo­id Se­ri­ali­ze(string fi­le­na­me) {

   &nb= sp;Stream stre­am;

   &nb= sp;IFormatter for­mat­ter =3D new Bi­nar­y­For­mat­ter();

   &nb= sp;try {

   &nb= sp;stream =3D new Fi­leS­t­re­am(fi­le­na­me, Fi­le­Mo­de.Cre­ate, Fi­le­Ac­cess.Wri­= te, Fi­leS­ha­re.No­ne);

   &nb= sp;}

   &nb= sp;catch (Excep­ti­on ex) { throw ex; }

   &nb= sp;try {

   &nb= sp;for (int i =3D 0; i ‹ Da­ta­Obj­ec­ts.Co­unt; i++) {

   &nb= sp;formatter.Serialize(stream, Da­ta­Obj­ec­ts[i]);

   &nb= sp;}

   &nb= sp;}

   &nb= sp;catch (Excep­ti­on ex) { throw ex; }

   &nb= sp;finally { stre­am.Clo­se(); }

   &nb= sp;}

    

    Как за­пус­тить де­се­ри­али­з= а­цию:

    

   &nb= sp;internal vo­id De­Se­ri­ali­ze(string fi­le­na­me, Mo­use­Even­t­Han­d­ler mo­use­Do= wn) {

   &nb= sp;IFormatter for­mat­ter =3D new Bi­nar­y­For­mat­ter();

   &nb= sp;Stream stre­am;

   &nb= sp;try {

   &nb= sp;stream =3D new Fi­leS­t­re­am(fi­le­na­me, Fi­le­Mo­de.Open, Fi­le­Ac­cess.Re­ad, = Fi­leS­ha­re.Re­ad);

   &nb= sp;}

   &nb= sp;catch (Excep­ti­on ex) {

   &nb= sp;throw ex;

   &nb= sp;}

   &nb= sp;object obj;

   &nb= sp;try {

   &nb= sp;while (stre­am.Po­si­ti­on ‹ stre­am.Length-1) {

   &nb= sp;obj =3D for­mat­ter.De­se­ri­ali­ze(stre­am);

   &nb= sp;// ус­та­нав­ли­ва­ем event, ес­ли на­до

   &nb= sp;((SerializableClass)obj).MouseDown +=3D mo­use­Down;

   &nb= sp;// вно­сим объ­ект в прог­рам­му

   &nb= sp;DataObjects.Add((SerializableClass)obj);

   &nb= sp;}

   &nb= sp;}

   &nb= sp;catch (Excep­ti­on ex) {

   &nb= sp;throw ex;

   &nb= sp;}

   &nb= sp;finally { stre­am.Clo­se(); }

   &nb= sp;}

    

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

    Помимо это­го вы мо­же­те сде­= лать свой се­ри­али­за­тор, за­да­ча дол­гая, нуд­ная, но мо­жет при­нес­ти не­м= а­лые вы­го­ды. Для справ­ки: се­ри­али­за­ция Int32 за­ни­ма­ет 54 бай­та, да­же= в би­нар­ном ви­де, пос­коль­ку 50 байт ухо­дят на наз­ва­ние клас­са. Так что иног­да луч­ше поль­зо­вать­ся сво­ими се­ри­али­за­то­ра­ми.

    

    Итоги:

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

    Основные ми­ну­сы - боль­шие о= бъ­емы, и не­об­хо­ди­мость час­то вруч­ную пе­ре­чис­лять тех, ко­го на­до выг­ру­= жать.

    

Гла­ва 3. Ref­lec­ti­on.

  &nb= sp; Перевод с ком­мен­та­ри­ями статьи с codeproject.com: http://www.c= odeproject.com/csharp/introreflection.asp. Это не пол­ный пе­ре­вод статьи, а толь­ко пе­рес­каз тех мест, ко­то­рые я сче= л са­мы­ми важ­ны­ми.

    

    Введение

    Reflection - зна­чи­тел= ь­ное но­вов­ве­де­ние в .NET. Че­рез Ref­lec­ti­on прог­рам­мы со­би­ра­ют и ра­= бо­та­ют со сво­ими ме­та­дан­ны­ми. Это мощ­ный ме­ха­низ для изу­че­ния as­sembly и раз­ных объ­ек­тов во вре­мя ра­бо­ты прог­рам­мы. Рас­смат­ри­ва­емое API = на­хо­дит­ся в System.Ref­lec­ti­on na­mes­pa­ce. Че­рез Ref­lec­ti­on мож­но по­лу­чить= ин­фор­ма­цию о клас­сах, ме­то­дах, свой­ст­вах и со­бы­ти­ях лю­бо­го объ­ек­та, а так­= же мож­но вы­зы­вать ме­то­ды. И т.д.

    

    Использование Ref­lec­ti­on= для по­лу­че­ния спис­ка ис­поль­зу­емых as­sembly

    

    

   &nb= sp;using System;

   &nb= sp;using System.Ref­lec­ti­on;

   &nb= sp;namespace Ref­lec­ti­on­De­moC­S­harp

   &nb= sp;{

   &nb= sp;class Re­fe­ren­ce­dAs­sem­b­li­es

   &nb= sp;{

   &nb= sp;[STAThread]

   &nb= sp;static vo­id Ma­in(string[] args)

   &nb= sp;{

   &nb= sp;Assembly[] ap­pAs­sem­b­li­es =3D

   &nb= sp;System.AppDomain.CurrentDomain.GetAssemblies ();

   &nb= sp;foreach (Assembly as­sembly in ap­pAs­sem­b­li­es )

   &nb= sp;{

   &nb= sp;Console.WriteLine (assem­b­ly.Ful­lNa­me );

   &nb= sp;}

   &nb= sp;Console.ReadLine ();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

    

    Вывод будет таким:

    

   &nb= sp;mscorlib, Ver­si­on=3D1.0.5000.0, Cul­tu­re=3Dne­ut­ral, Pub­lic­Key­To­ken=3Db77a5c5= 61934e089

   &nb= sp;ReflectionDemoCSharp, Ver­si­on=3D1.0.1882.29904, Cul­tu­re=3Dne­ut­ral, Pub­lic­Key­To­ken=3Dnul= l

    

    Класс System.AppDo­ma­in class= пред­с­тав­ля­ет до­мен при­ло­же­ния, ко­то­рый яв­ля­ет­ся не­ко­ей изо­ли­ро­ван­ной об­л= ас­тью, в ко­то­рой ис­пол­ня­ет­ся при­ло­же­ние.

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

    Метод Ge­tAs­sem­b­li­es() воз= ­в­ра­ща­ет спи­сок as­sembly, заг­ру­жен­ных в до­мен Ref­lec­ti­on­De­moC­S­harp.

    

    Исследование as­sembly и по= ­лу­че­ние спис­ка ти­пов

    Для на­ча­ла, ди­на­ми­чес­ки = заг­ру­жа­ем as­sembly, че­рез As­sem­b­ly.Lo­ad().

    

   &nb= sp;public sta­tic As­sem­b­ly.Lo­ad(Assem­b­l­y­Na­me)

    Передаем MsCor­Lib.dll.

    

   &nb= sp;Assembly Lo­ade­dAs­sembly =3D As­sem­b­ly.Lo­ad("mscor­lib.dll");

    

    Когда as­sembly заг­ру­же­на, = мо­жем вос­поль­зо­вать­ся ме­то­дом Get­T­y­pes(), что­бы по­лу­чить мас­сив ти­п= ов.

    

   &nb= sp;System.Type[] Exis­tin­g­T­y­pes =3D Lo­ade­dAs­sem­b­ly.Get­T­y­pes ();

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

    

   &nb= sp;using System;

   &nb= sp;using System.Ref­lec­ti­on ;

   &nb= sp;namespace Ref­lec­ti­on­De­moC­S­harp

   &nb= sp;{

   &nb= sp;class Ref­lec­ted­T­y­pes

   &nb= sp;{

   &nb= sp;[STAThread]

   &nb= sp;static vo­id Ma­in(string[] args)

   &nb= sp;{

   &nb= sp;Assembly Lo­ade­dAs­sembly =3D As­sem­b­ly.Lo­ad ("mscor­lib.dll");

   &nb= sp;System.Type[] Exis­tin­g­T­y­pes =3D Lo­ade­dAs­sem­b­ly.Get­T­y­pes ();

   &nb= sp;foreach(Type type in Exis­tin­g­T­y­pes)

   &nb= sp;Console.WriteLine (type.ToS­t­ring ());

   &nb= sp;Console.WriteLine (Exis­tin­g­T­y­pes.Length +

   &nb= sp;" Types Dis­co­ve­red in mscor­lib.dll");

   &nb= sp;Console.ReadLine ();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

    

    Вывод (час­тич­ный­):

    

   &nb= sp;System.Object

   &nb= sp;System.ICloneable

   &nb= sp;System.Collections.IEnumerable

   &nb= sp;System.Collections.ICollection

   &nb= sp;System.Collections.IList

   &nb= sp;System.Array

   &nb= sp;System.Array+SorterObjectArray

   &nb= sp;System.Array+SorterGenericArray

   &nb= sp;System.Collections.IEnumerator

   &nb= sp;1480 Types Dis­co­ve­red in mscor­lib.dll

    

    Исследование ти­па

    Попробуем по­лу­чить спи­сок в= сех чле­нов кон­к­рет­но­го ти­па. Ме­тод Type.Get­T­y­pe(Type­Na­me) воз­в­ра­= ща­ет объ­ект System.Type, со­от­вет­с­т­ву­ющий стро­ке ар­гу­мен­та. Мы зап­ро­= сим тип с по­мощью ме­то­да Type.Get­Mem­bers(), что­бы по­лу­чить мас­сив его = чле­нов.

    Я уже рас­ска­зы­вал, что в= мес­то Type.Get­T­y­pe("System.Int32") мож­но ис­поль­зо­вать type­of(in= t).

    

   &nb= sp;using System;

   &nb= sp;using System.Ref­lec­ti­on ;

   &nb= sp;namespace Ref­lec­ti­on­De­moC­S­harp

   &nb= sp;{

   &nb= sp;class Ref­lec­ted­T­y­pes

   &nb= sp;{

   &nb= sp;[STAThread]

   &nb= sp;static vo­id Ma­in(string[] args)

   &nb= sp;{

   &nb= sp;Type Type­To­Ref­lect =3D Type.Get­T­y­pe("System.Int32");<= /span>

   &nb= sp;System.Reflection.MemberInfo[] Mem­bers =3Dtype.Get­Mem­bers();

   &nb= sp;Console.WriteLine ("Mem­bers of "+Type­To­Ref­lect.ToS­t­ring ());

   &nb= sp;Console.WriteLine();

   &nb= sp;foreach (Mem­be­rIn­fo mem­ber in Mem­bers )

   &nb= sp;Console.WriteLine(member);

   &nb= sp;Console.ReadLine ();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

    Вывод:

    

   &nb= sp;Members of System.Int32

   &nb= sp;Int32 Max­Va­lue

   &nb= sp;Int32 Min­Va­lue

   &nb= sp;System.String ToS­t­ring(System.IFor­mat­P­ro­vi­der)

   &nb= sp;System.TypeCode Get­T­y­pe­Co­de()

   &nb= sp;System.String ToS­t­ring(System.String, System.IFor­mat­P­ro­vi­der)

   &nb= sp;Int32 Com­pa­re­To(System.Obj­ect)

   &nb= sp;Int32 Get­Has­h­Co­de()

   &nb= sp;Boolean Equ­als(System.Obj­ect)

   &nb= sp;System.String ToS­t­ring()

   &nb= sp;System.String ToS­t­ring(System.String)

   &nb= sp;Int32 Par­se(System.String)

   &nb= sp;Int32 Par­se(System.String, System.Glo­ba­li­za­ti­on.Num­ber­S­t­y­les)

   &nb= sp;Int32 Par­se(System.String, System.IFor­mat­P­ro­vi­der)

   &nb= sp;Int32 Par­se(System.String, System.Glo­ba­li­za­ti­on.Num­ber­S­t­y­les, System.I= For­mat­P­ro­vi­der)

   &nb= sp;System.Type Get­T­y­pe()

    * System.Ref­lec­ti­on.Mem­be­= rIn­fo[] Mem­bers =3Dtype.Get­Mem­bers() - воз­в­ра­ща­ет всех чле­нов ти­па.

    * System.Ref­lec­ti­on.Met­ho­= dIn­fo[] Met­hods =3DType.Get­Met­hods() - воз­в­ра­ща­ет толь­ко ме­то­ды ти­па.

    * System.Ref­lec­ti­on.Fi­el­d= In­fo[] Fi­elds =3DType.Get­Fi­elds() - воз­в­ра­ща­ет толь­ко по­ля ти­па.

    * System.Ref­lec­ti­on.Pro­per= ­t­yIn­fo[] Pro­per­ti­es =3D type.Get­P­ro­per­ti­es () - воз­в­ра­ща­ет толь­ко свой­= ст­ва.

    * System.Ref­lec­ti­on.Even­tI= n­fo[] Events =3D type.Ge­tE­vents() - воз­в­ра­ща­ет со­бы­тия ти­па.

    * System.Ref­lec­ti­on.Con­s­t= ­ruc­to­rIn­fo[] Con­s­t­ruc­tors =3D type.Get­Con­s­t­ruc­tors () - воз­в­ра­ща­ет кон­с­т­= рук­то­ры ти­па.

    * System.Type[] In­ter­fa­ces = =3D type.Ge­tIn­ter­fa­ces() - воз­в­ра­ща­ет ин­тер­фей­сы ти­па.

    

    Динамический вы­зов, ис­пол= ь­зуя Type.Invo­ke­Mem­ber()

    Рассмотрим, как ди­на­ми­чес­к= и выз­вать ме­тод, ис­поль­зуя type.Invo­ke­Mem­ber(). type.Invo­ke­Mem­ber() поз­во­л= я­ет вы­зы­вать ме­то­ды по их наз­ва­ни­ям.

    

    Аргументы In­vo­ke­Mem­ber():<= /p>

    1. Имя вы­зы­ва­емо­го ме­то­д= а. Пе­ре­да­ет­ся стро­кой.

    2. Чле­ны пе­ре­чис­ле­ния Bin= ­din­g­F­lags. Пе­ре­чис­ле­ние Bin­din­g­F­lags оп­ре­де­ля­ет фла­ги, ко­то­рые кон­т­ро= ­ли­ру­ют свя­зы­ва­ние и спо­соб по­ис­ка ти­пов и чле­нов.Нап­ри­мер, ис­кать ли= сре­ди sta­tic ме­то­дов и т.п.

    3. Объ­ект Bin­der, оп­ре­де­л= я­ющий свой­ст­ва и про­цесс свя­зы­ва­ния. Мо­жет быть null, для ис­поль­зо­ва­ния Bin­der по умол­ча­нию. Этот па­ра­метр поз­во­ля­ет поль­зо­ва­те­лю по­лу= ­чить внеш­ний кон­т­роль над спо­со­бом вы­бо­ра пе­ре­оп­ре­де­лен­ных фун­к­ци= й и ме­то­да­ми пре­об­ра­зо­ва­ния ар­гу­мен­тов.

    4. Объ­ект, для ко­то­ро­го на= ­до выз­вать ме­тод.

    5. Мас­сив ар­гу­мен­тов, пе­р= е­да­ва­емых ме­то­ду.

    

   &nb= sp;using System;

   &nb= sp;using System.Ref­lec­ti­on ;

   &nb= sp;namespace Ref­lec­ti­on­De­moC­S­harp

   &nb= sp;{

   &nb= sp;class Ref­lec­ted­T­y­pes

   &nb= sp;{

   &nb= sp;[STAThread]

   &nb= sp;static vo­id Ma­in(string[] args)

   &nb= sp;{

   &nb= sp;Type Type­To­Ref­lect =3D Type.Get­T­y­pe("System.String");=

   &nb= sp;object re­sult =3D null;

   &nb= sp;object[] ar­gu­ments =3D {"abc","xyz"};

   &nb= sp;result =3D Type­To­Ref­lect.Invo­ke­Mem­ber ("Equ­als",

   &nb= sp;BindingFlags.InvokeMethod, null, re­sult, ar­gu­ments);

   &nb= sp;Console.WriteLine (re­sult.ToS­t­ring ());

   &nb= sp;Console.ReadLine ();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

    Вывод бу­дет "fal­se"= ;.

    

    Дальше в статье рас­ска­зы­= ва­ет­ся о тех­но­ло­гии Ref­lec­ti­on.Emit, ко­то­рая поз­во­ля­ет соз­да­вать as­s= embly на ле­ту... Чес­т­но го­во­ря, я этой тех­но­ло­гии ви­жу толь­ко од­но при= ­ме­не­ние - соз­да­ние соб­с­т­вен­ных ком­пи­ля­то­ров. Впро­чем, сей­час мод­но вст= ав­лять в прог­рам­мы воз­мож­ность до­пи­сы­ва­ния сво­их пла­ги­нов пря­мо из ок­= на прог­рам­мы... а пос­коль­ку пла­ги­ны про­ще пи­сать на внут­рен­нем язы­ке при­ло­же­ния, то на­вер­ное и соб­с­т­вен­ный ком­пи­ля­тор мо­жет быть по= ­ле­зен. Хо­тя не знаю, есть же вся­кие скрип­то­вые ком­пи­ля­то­ры, уже вклю­чен­н= ые в сос­тав .net, есть тех­но­ло­гия VBA, ко­то­рую мно­гие не лю­бят, но ху­же= она от это­го не ста­но­вить­ся и т.д.

    

Глава 4. Самодельные элементы управления 1.<= /b>

  &nb= sp; 

    Элемент уп­рав­ле­ния - это лю= ­бой ку­сок ин­тер­фей­са, ко­то­рый са­жа­ет­ся в ок­но и как-то ре­аги­ру­ет на поль­зо­ва­те­ля. Вся­кие кноп­ки, ме­нюш­ки, спис­ки и про­чее.

    Часто бы­ва­ет си­ту­ация, ког= ­да хо­чет­ся (или тре­бу­ет­ся) как-то рас­ши­рить воз­мож­нос­ти эле­мен­та. Нап­ри­мер= , хо­чет­ся сде­лать дру­гую ри­сов­ку пун­к­тов ме­ню, или вмес­то од­но­го пол­зун­ка= нуж­но сде­лать два - для ус­та­нов­ки ми­ни­му­ма, мак­си­му­ма.

    В слу­ча­ях, ког­да на­до из­м= е­нить ри­сов­ку, сна­ча­ла нуж­но пос­мот­реть - нель­зя ли это сде­лать с по­мощ= ью род­ных ме­то­дов эле­мен­та. Нап­ри­мер, все пун­к­ты ме­ню, вы­па­да­ющих спис­ков и по­доб­ные им име­ют со­бы­тие Dra­wI­tem, в ко­то­ром мож­но оп= и­сы­вать свою фун­к­цию ри­со­ва­ния объ­ек­та.

    Рассмотрим на при­ме­ре. Нам н= а­до сде­лать вы­па­да­ющее ок­но, каж­дый пункт спис­ка ко­то­ро­го сос­то­ит из чис­ла, тек­с­та и цве­та.

    Класс хра­не­ния дан­ных:

    

   &nb= sp;public class Com­bo­Da­ta

   &nb= sp;{

   &nb= sp;public int num­ber; // хра­ни­мое чис­ло

   &nb= sp;public string text; // текст

   &nb= sp;public Co­lor co­lor; // и цвет

   &nb= sp;public Com­bo­Da­ta() { // пус­той кон­с­т­рук­тор

   &nb= sp;number =3D 0;

   &nb= sp;text =3D "";

   &nb= sp;color =3D Co­lor.Black;

   &nb= sp;}

   &nb= sp;public Com­bo­Da­ta(int in­Num­ber, string in­Text, Co­lor in­Co­lor) { // па­ра­м= ет­ри­чес­кий кон­с­т­рук­тор

   &nb= sp;number =3D in­Num­ber;

   &nb= sp;text =3D in­Text;

   &nb= sp;color =3D in­Co­lor;

   &nb= sp;}

   &nb= sp;public over­ri­de string ToS­t­ring() { // ме­тод для вы­во­да в стан­дар­т­ное вы= ­па­да­ющее ок­но

   &nb= sp;return String.For­mat("{0}, {1}, {2}", num­ber, text, co­lor);

   &nb= sp;}

   &nb= sp;}

    

    Начальные дан­ные:

    

   &nb= sp;public Form1() {

   &nb= sp;InitializeComponent();

   &nb= sp;items =3D new Com­bo­Da­ta[5];

   &nb= sp;items[0] =3D new Com­bo­Da­ta(0, "text1", Co­lor.Red);

   &nb= sp;items[1] =3D new Com­bo­Da­ta(1, "text2", Co­lor.Blue);<= /i>

   &nb= sp;items[2] =3D new Com­bo­Da­ta(2, "text3", Co­lor.Gre­en);

   &nb= sp;items[3] =3D new Com­bo­Da­ta(3, "text4", Co­lor.Ma­gen­ta);

   &nb= sp;items[4] =3D new Com­bo­Da­ta(4, "text5", Co­lor.Fro­mArgb(00, 221, 238));=

   &nb= sp;comboBox1.Items.AddRange(items);

   &nb= sp;}

   &nb= sp;public Com­bo­Da­ta[] items;

    

    В об­щем-то, мож­но и так ос­т= а­вить - бу­дет в вы­па­да­ющем ок­не та­кая кар­ти­на:

  &nb= sp; 

    Но как-то оно не пра­виль­но п= о­ка­зы­вать цвет циф­ра­ми. По­это­му, за­да­ем com­bo­Box свой­cт­во Draw­Mo­de =3D Ow= ­ner­D­raw­Fi­xed и про­пи­сы­ва­ем со­бы­тие Dra­wI­tem:

    

   &nb= sp;private vo­id com­bo­Box1_Dra­wI­tem(obj­ect sen­der, Dra­wI­te­mE­ven­tArgs e) {

   &nb= sp;SolidBrush br =3D new So­lid­B­rush(items[e.Index].co­lor); // соз­да­ем кисть цве­та = эле­мен­та, ко­то­рый ри­су­ем

   &nb= sp;if (e.Sta­te =3D=3D Dra­wI­tem­S­ta­te.Se­lec­ted) { // ес­ли эле­мент выб­ран=

   &nb= sp;e.Graphics.FillRectangle(br, e.Bo­unds); // зак­ра­ши­ва­ем его на­шим цве­том

   &nb= sp;e.Graphics.DrawString(items[e.Index].ToString(), e.Font, System­B­rus­hes.Hig­h­lig­h­t­Text, e.Bo­un­ds.Lo­ca­ti­on); // по= ­верх ри­су­ем текст

   &nb= sp;e.DrawFocusRectangle();

   &nb= sp;}

   &nb= sp;else { // ес­ли эле­мент не выб­ран

   &nb= sp;e.DrawBackground(); // ри­су­ем фон

   &nb= sp;e.Graphics.DrawString(items[e.Index].ToString(), e.Font, System­B­rus­hes.Me­nu­Text, e.Bo­un­ds.Lo­ca­ti­on); // пи­шем стр= о­ку

   &nb= sp;}

   &nb= sp;br.Dispose(); // ос­во­бож­да­ем кисть

   &nb= sp;}

        Теперь= это выг­ля­дит так:

  &nb= sp; 

    Принцип, я ду­маю, по­ня­тен.<= /p>

    

    Иногда бы­ва­ет на­до сде­лать= на­бор групп эле­мен­тов. Мож­но пой­ти прос­тым пу­тем, и соз­да­вать каж­дый эле= ­мент в от­дель­нос­ти, а мож­но соз­дать эле­мент уп­рав­ле­ния Груп­па и в про­= цес­се ра­бо­ты прог­рам­мы соз­да­вать его.

    Рассмотрим на том же при­ме­ре= - на­до вы­вес­ти все зна­че­ния в ви­де тек­с­то­вых окон и па­не­лек цве­та. Ва­р= и­ант 1:

    

   &nb= sp;internal vo­id Cre­ate­Con­t­rols() { // фун­к­ция соз­да­ния эле­мен­тов уп­рав­ле­= ния

   &nb= sp;TextBox tb;

   &nb= sp;Panel p;

   &nb= sp;SuspendLayout();

   &nb= sp;for (int i =3D 0; i ‹ items.Length; i++) {

   &nb= sp;tb =3D new Tex­t­Box(); // тек­с­то­вое по­ле для чис­ла

   &nb= sp;tb.Location =3D new Po­int(20, 50 + i * 25); // по­зи­ция

   &nb= sp;tb.Size =3D new Si­ze(50, 20); // раз­мер

   &nb= sp;tb.Text =3D items[i].num­ber.ToS­t­ring(); // текст

   &nb= sp;Controls.Add(tb); // до­бав­ля­ем эле­мент в ок­но

   &nb= sp;tb =3D new Tex­t­Box(); // тек­с­то­вое по­ле для тек­с­та

   &nb= sp;tb.Location =3D new Po­int(75, 50 + i * 25);

   &nb= sp;tb.Size =3D new Si­ze(50, 20);

   &nb= sp;tb.Text =3D items[i].text;

   &nb= sp;Controls.Add(tb);

   &nb= sp;p =3D new Pa­nel(); // па­нель для цве­та

   &nb= sp;p.Location =3D new Po­int(130, 50 + i * 25);

   &nb= sp;p.Size =3D new Si­ze(50, 20);

   &nb= sp;p.BackColor =3D items[i].co­lor; // за­да­ем цвет

   &nb= sp;p.Tag =3D i; // ус­та­нав­ли­ва­ем иден­ти­фи­ка­тор

   &nb= sp;p.Click +=3D new Even­t­Han­d­ler(p_Click); // ус­та­нав­ли­ва­ем со­бы­тие клик

   &nb= sp;Controls.Add(p);

   &nb= sp;}

   &nb= sp;ResumeLayout(false);

   &nb= sp;}

    Добавим в Form1() пос­лед­ней = стро­кой за­пуск фун­к­ции соз­да­ния эле­мен­тов

    

   &nb= sp;CreateControls();

    И про­пи­шем со­бы­тие клик дл= я па­не­лей­:

    

   &nb= sp;void p_Click(obj­ect sen­der, Even­tArgs e) {

   &nb= sp;int idx =3D (int)((Pa­nel)sen­der).Tag; // но­мер па­не­ли

   &nb= sp;MessageBox.Show(String.Format("color pa­nel of item {0} just has be­en clic­ked", idx)); // со­об­ще­ние

   &nb= sp;}

       Вот так вот.=

  &nb= sp; 

     Вроде все в по­ряд­ке. С= тем же ус­пе­хом мож­но сде­лать свой эле­мент уп­рав­ле­ния, ко­то­рый бу­дет = сос­то­ять из двух тек­с­то­вых по­лей и од­ной па­нель­ки, и в фор­му до­бав­лять его. Соз­да­ем User­Con­t­rol, соз­да­ем на нем два тек­с­то­вых по­ля и па­нель= ­ку.

    Пишем два кон­с­т­рук­то­ра:

    

   &nb= sp;public User­Con­t­rol1(Com­bo­Da­ta item) { // прос­той кон­с­т­рук­тор=

   &nb= sp;InitializeComponent();

   &nb= sp;Num =3D item.num­ber;

   &nb= sp;Tex =3D item.text;

   &nb= sp;Col =3D item.co­lor;

   &nb= sp;}

    

   &nb= sp;public User­Con­t­rol1(Com­bo­Da­ta item, int pa­ne­lIdx, Even­t­Han­d­ler pa­nel­= C­lick) { // кон­с­т­рут­кор с под­дер­ж­кой кли­ка

   &nb= sp;InitializeComponent();

   &nb= sp;Num =3D item.num­ber;

   &nb= sp;Tex =3D item.text;

   &nb= sp;Col =3D item.co­lor;

   &nb= sp;panel1.Tag =3D pa­ne­lIdx;

   &nb= sp;panel1.Click +=3D pa­nel­C­lick;

   &nb= sp;}

    

    Прописываем по­ля дан­ных:

    

   &nb= sp;private int num; // чис­ло

   &nb= sp;public int Num { // свой­ст­во чис­ло

   &nb= sp;get { re­turn num; }

   &nb= sp;set {

   &nb= sp;num =3D va­lue;

   &nb= sp;textBox1.Text =3D num.ToS­t­ring(); // об­нов­ле­ние тек­с­то­во­го по­ля

   &nb= sp;}

   &nb= sp;}

   &nb= sp;private string tex; // текст

   &nb= sp;public string Tex { // свой­ст­во текст

   &nb= sp;get { re­turn tex; }

   &nb= sp;set {

   &nb= sp;tex =3D va­lue;

   &nb= sp;textBox2.Text =3D tex;

   &nb= sp;}

   &nb= sp;}

   &nb= sp;private Co­lor col; // цвет

   &nb= sp;public Co­lor Col { // свой­ст­во цвет

   &nb= sp;get { re­turn col; }

   &nb= sp;set {

   &nb= sp;col =3D va­lue;

   &nb= sp;panel1.BackColor =3D col; // об­нов­ле­ние цве­та

   &nb= sp;}

   &nb= sp;}

    

    В Form1 пи­шем фун­к­цию соз­д= а­ния на­ше­го эле­мен­та:

    

   &nb= sp;internal vo­id Cre­ate­User­Con­t­rols() {

   &nb= sp;UserControl1 uc;

   &nb= sp;SuspendLayout();

   &nb= sp;for (int i =3D 0; i ‹ items.Length; i++) {

   &nb= sp;uc =3D new User­Con­t­rol1(items[i], i, new Even­t­Han­d­ler(p_Click));=

   &nb= sp;uc.Location =3D new Po­int(20, 50 + i * 25);

   &nb= sp;uc.Tag =3D i;

   &nb= sp;Controls.Add(uc);

   &nb= sp;}

   &nb= sp;ResumeLayout(false);

   &nb= sp;}

    

    И в Form1() ме­ня­ем Cre­ate­C= on­t­rols() на Cre­ate­User­Con­t­rols().

    

    Все ра­бо­та­ет так же, но ис­= поль­зу­ет са­мо­дель­ную груп­пу уп­рав­ле­ния. Удоб­с­т­во это­го под­хо­да в двух м= о­мен­тах:

    1. Ес­ли вам по­на­до­бят­ся в= ся­кие фи­чи с вы­де­ле­ни­ем - в ва­шем рас­по­ря­же­нии со­бы­тие Pa­int для все= ­го эле­мен­та уп­рав­ле­ния, ра­моч­ку там сде­лать или еще че­го. При соз­да­= нии каж­до­го по­ля в от­дель­нос­ти по­доб­ные ра­моч­ки бу­дут де­лать­ся сло= ж­нее, да и ско­рость упа­дет за­мет­нее.

    2. Ес­ли та­ких эле­мен­тов уп= ­рав­ле­ния не 5, а 50, то ско­рость ра­бо­ты су­щес­т­вен­но по­вы­ша­ет­ся. Сис­те­ма= про­ве­ря­ет ви­ди­мость эле­мен­та уп­рав­ле­ния, и ес­ли он це­ли­ком не­ви­ден - она = не про­ве­ря­ет его до­чер­ние эле­мен­ты. Ста­ло быть, ес­ли ис­поль­зо­вать = та­кой ме­тод груп­пи­ров­ки - сис­те­ма бу­дет про­ве­рять 50 эле­мен­тов на ви­д= и­мость. А ес­ли по­ля при­пи­сы­вать фор­ме нап­ря­мую, как в ва­ри­ан­те 1, то сис= ­те­ма бу­дет про­ве­рять 150 эле­мен­тов.

    

    Ну и на­пос­ле­док: стан­дар­т= ­ный com­bo­Box име­ет со­бы­тие Se­lec­te­dIn­dex­C­han­ged, но он не со­об­ща­= ет в нем, ка­кой эле­мент был выб­ран до сме­ны. Поп­ро­бу­ем мо­ди­фи­ци­ро­вать стан­дар­т­ный com­bo­Box так, что­бы он в этом со­бы­тии со­об­щал о пре­д= ы­ду­щем зна­че­нии.

    Создаем оче­ред­ной User­Con­t= ­rol и пе­ре­хо­дим в код, не об­ра­щая вни­ма­ния на ди­зай­нер. Ста­вим ро­ди­те= ­лем на­ше­го эле­мен­та класс Com­bo­Box вмес­то User­Con­t­rol:

    

   &nb= sp;public par­ti­al class User­Con­t­rol3 : System.Win­dows.For­ms.Com­bo­Box

    

    Прописываем пе­ре­мен­ную для = хра­не­ния пре­ды­ду­ще­го зна­че­ния выб­ран­но­го ин­дек­са:

    

   &nb= sp;private int se­lec­te­dIn­dex_prev;

    

    В кон­с­т­рук­тор до­бав­ля­ем= ини­ци­али­за­цию этой пе­ре­мен­ной­:

    

   &nb= sp;selectedIndex_prev =3D -1;

    

    И пе­ре­пи­сы­ва­ем со­бы­тие = Se­lec­te­dIn­dex­C­han­ged:

    

   &nb= sp;protected over­ri­de vo­id On­Se­lec­te­dIn­dex­C­han­ged(Even­tArgs e) {<= /span>

   &nb= sp;base.OnSelectedIndexChanged(new User­Con­t­rol2Index­C­han­ge­dE­ven­tAr­gs(se­lec­te­dIn­dex_prev)); // ст= ан­дар­т­ный об­ра­бот­чик, но с на­шим клас­сом ар­гу­мен­тов со­бы­тия

   &nb= sp;selectedIndex_prev =3D this.Se­lec­te­dIn­dex; // из­ме­нить пред. ин­декс

   &nb= sp;}

    

    Создаем от­дель­ный класс для = ар­гу­мен­тов на­ше­го со­бы­тия:

    

   &nb= sp;public class User­Con­t­rol2Index­C­han­ge­dE­ven­tArgs : Even­tArgs

   &nb= sp;{

   &nb= sp;public User­Con­t­rol2Index­C­han­ge­dE­ven­tAr­gs(int pre­vIdx) {

   &nb= sp;prevSelectedIndex =3D pre­vIdx;

   &nb= sp;}

   &nb= sp;public int prev­Se­lec­te­dIn­dex;

   &nb= sp;}

    

    Вот и все. Для про­вер­ки до­б= а­вим в Form1 наш ви­до­из­ме­нен­ный com­bo­Box и про­пи­шем со­бы­тие Se­lec­te= ­dIn­dex­C­han­ged:

    

   &nb= sp;private vo­id user­Con­t­rol31_Se­lec­te­dIn­dex­C­han­ged(obj­ect sen­der, Even­tA= rgs e) {

   &nb= sp;MessageBox.Show(String.Format("Selected in­dex was chan­ged from {0} to {1}", ((User­Con­t­rol2Index­C­han­ge­= dE­ven­tAr­gs)e).prev­Se­lec­te­dIn­dex, user­Con­t­rol31.Se­lec­te­dIn­dex));

   &nb= sp;}

    

    Не за­бы­ва­ем в кон­с­т­рук­т= ор фор­мы до­ба­вить за­пол­не­ние на­ше­го com­bo­Box'a зна­че­ни­ями:

    

   &nb= sp;userControl31.Items.AddRange(items);

    Каталог про­ек­та для MS Vi­su= ­al Stu­dio 2005 с при­ме­ром на­хо­дит­ся в ар­хи­ве при­ме­ров, под­ка­та­лог user­con­t­rol1.

    Архив при­ме­ров мож­но ска­ча= ть здесь: http://www.ro= ­bin­land.com/csharp-ba­sis/sam­p­les.zip.

    

Глава 5. Самодельные элементы управления 2.<= /b>

  &nb= sp; В предыдущей главе мы рас­смот­ре­ли си­ту­ации при ко­то­рых мож­но ком= ­би­ни­ро­вать уже су­щес­т­ву­ющие эле­мен­ты уп­рав­ле­ния, или дос­та­точ­но прос­то мо= ­ди­фи­ци­ро­вать их. Од­на­ко, бы­ва­ют си­ту­ации ког­да ни­ка­кой мо­ди­фи­ка­ци­ей нуж­ны= й ре­зуль­тат не бу­дет дос­тиг­нут. Рас­смот­рим од­ну та­кую на при­ме­ре.

    Необходимо сде­лать эле­мент у= п­рав­ле­ния, поз­во­ля­ющий ус­та­нав­ли­вать два зна­че­ния в за­дан­ном ин­тер­ва­ле. = Для ус­та­нов­ки од­но­го зна­че­ния есть род­ной эле­мент уп­рав­ле­ния - sli­= der (или Trac­k­Bar). Ле­ни­вые лю­ди ска­жут, что не­че­го тут из­в­ра­щать­ся, дос­та­точ­но по­са­дить два слай­де­ра и не мо­ро­чить го­ло­ву. :) С дру­= гой сто­ро­ны, сде­лать свой эле­мент уп­рав­ле­ния дос­та­точ­но прос­то, а ин= ­тер­фей­с он мо­жет зна­чи­тель­но улуч­шить, ибо чем мень­ше эле­мен­тов в ок­не - т= ем луч­ше.

    

  &nb= sp; Приступим.

    К про­ек­ту при­со­еди­ня­ем U= ser­Con­t­rol и за­да­ем ос­нов­ные свой­ст­ва:

    

   &nb= sp;this.MaximumSize =3D new System.Dra­wing.Si­ze(3000, 15); // мак­си­маль­ный раз­мер

   &nb= sp;this.MinimumSize =3D new System.Dra­wing.Si­ze(50, 15); // ми­ни­маль­ный раз­мер=

   &nb= sp;this.Size =3D new System.Dra­wing.Si­ze(50, 15); // стар­то­вый раз­мер

    

    Наш эле­мент уп­рав­ле­ния, с = точ­ки зре­ния ри­со­ва­ния, сос­то­ит из вдав­лен­ной по­лос­ки и двух пол­зун­ко= в. Каж­дый пол­зу­нок мо­жет быть в трех сос­то­яни­ях - нор­маль­ном, под­с­в= е­чен­ном (ког­да мыш­ка над ним) и ак­тив­ном (ког­да его та­щат). за­да­дим со­от­в= ет­с­т­ву­ющие фла­ги. А за­од­но соз­да­дим пря­мо­уголь­ни­ки пол­зун­ков, что­бы про­ще= бы­ло по­том ри­со­вать и от­с­ле­жи­вать:

    

   &nb= sp;private bo­ol sli­der1drag; // пол­зу­нок 1 та­щат

   &nb= sp;private bo­ol sli­der2drag; // пол­зу­нок 2 та­щат

   &nb= sp;private bo­ol sli­der1ho­ver; // пол­зу­нок 1 под­с­ве­чен

   &nb= sp;private bo­ol sli­der2ho­ver; // пол­зу­нок 2 под­с­ве­чен

   &nb= sp;private Rec­tan­g­le sli­der1; // пол­зу­нок 1

   &nb= sp;private Rec­tan­g­le sli­der2; // пол­зу­нок 2

    

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

    

   &nb= sp;private vo­id Draw­Rec­tan­g­le3D(Grap­hics gr, int x, int y, int width, int he­igh= t, bo­ol pus­hed) {

   &nb= sp;if (pus­hed) {

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, x + 1, y + he­ight, x + width, y + he­ight);

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, x + width, y + he­ight, x + width, y + 1);

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, x, y, x + width, y);

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, x, y, x, y + he­ight);

   &nb= sp;}

   &nb= sp;else {

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, x + 1, y + he­ight, x + width, y + he­ight);

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, x + width, y + he­ight, x + width, y + 1);

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, x, y, x + width, y);

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, x, y, x, y + he­ight);

   &nb= sp;}

   &nb= sp;}

    Рисуем сис­тем­ны­ми цве­та­ми= , что­бы не вы­би­вать­ся из ус­та­нов­лен­но­го в сис­те­ме сти­ля офор­м­ле­ния, х= о­тя бы по цве­ту.

    И ва­ри­ант фун­к­ции для ра­б= о­ты с объ­ек­том Rec­tan­g­le:

    

   &nb= sp;private vo­id Draw­Rec­tan­g­le3D(Grap­hics gr, Rec­tan­g­le rect, bo­ol pus­hed) {=

   &nb= sp;if (pus­hed) {

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, rect.X + 1, rect.Bot­tom, rect.Right, rect.Bot­tom);<= /p>

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, rect.Right, rect.Bot­tom, rect.Right, rect.Y + 1);

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, rect.X, rect.Y, rect.Right, rect.Y);

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, rect.X, rect.Y, rect.X, rect.Bot­tom);

   &nb= sp;}

   &nb= sp;else {

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, rect.X + 1, rect.Bot­tom, rect.Right, rect.Bot­tom);<= /p>

   &nb= sp;gr.DrawLine(SystemPens.ControlDarkDark, rect.Right, rect.Bot­tom, rect.Right, rect.Y + 1);

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, rect.X, rect.Y, rect.Right, rect.Y);

   &nb= sp;gr.DrawLine(SystemPens.ControlLightLight, rect.X, rect.Y, rect.X, rect.Bot­tom);

   &nb= sp;}

   &nb= sp;}

    Теперь соз­да­ем об­ра­бот­чик= со­бы­тие Pa­int и опи­сы­ва­ем его:

    

   &nb= sp;private vo­id User­Con­t­rol1_Pa­int(obj­ect sen­der, Pa­in­tE­ven­tArgs e) {<= /o:p>

   &nb= sp;DrawRectangle3D(e.Graphics, 2, He­ight / 2 - 2, Width - 4, 4, true); // на­ри­со­вать утоп­лен­ную по­л= ос­ку

   &nb= sp;if (e.Clip­Rec­tan­g­le.Inter­sec­t­s­With(sli­der1)) { // ес­ли об­нов­ля­емы= й ре­ги­он пе­ре­се­ка­ет пол­зу­нок 1

   &nb= sp;if (sli­der1drag) { // ес­ли пол­зу­нок 1 та­щат

   &nb= sp;e.Graphics.FillRectangle(SystemBrushes.ControlDark, sli­der1); // зак­ра­сить его тем­ным цве­том

   &nb= sp;}

   &nb= sp;else if (sli­der1ho­ver) { // ес­ли пол­зу­нок под­с­ве­чен

   &nb= sp;e.Graphics.FillRectangle(SystemBrushes.ButtonHighlight, sli­der1); // зак­ра­сить его цве­том под­с­вет­ки

   &nb= sp;}

   &nb= sp;else { // ес­ли прос­то на­ри­со­вать

   &nb= sp;e.Graphics.FillRectangle(SystemBrushes.Control, sli­der1); // зак­ра­сить сис­тем­ным цве­том эле­мен­та уп­рав­ле­ния=

   &nb= sp;}

   &nb= sp;DrawRectangle3D(e.Graphics, sli­der1, fal­se); // на­ри­со­вать объ­ем­ную вы­пук­лую рам­ку=

   &nb= sp;}

   &nb= sp;if (e.Clip­Rec­tan­g­le.Inter­sec­t­s­With(sli­der2)) { // то же для пол­зун­к= а 2

   &nb= sp;if (sli­der2drag) {

   &nb= sp;e.Graphics.FillRectangle(SystemBrushes.ControlDark, sli­der2);

   &nb= sp;}

   &nb= sp;else if (sli­der2ho­ver) {

   &nb= sp;e.Graphics.FillRectangle(SystemBrushes.ButtonHighlight, sli­der2);

   &nb= sp;}

   &nb= sp;else {

   &nb= sp;e.Graphics.FillRectangle(SystemBrushes.Control, sli­der2);

   &nb= sp;}

   &nb= sp;DrawRectangle3D(e.Graphics, sli­der2, fal­se);

   &nb= sp;}

   &nb= sp;}

    

    Теперь на­до за­дать пря­мо­уг= оль­ни­ки пол­зун­ков и все зна­че­ния на ко­то­рые они опи­ра­ют­ся. Сна­ча­ла не­об= ­хо­ди­мые зна­че­ния:

    Минимум по­лос­ки слай­де­ра:<= /p>

    

   &nb= sp;private int min­Val; // по­ле для внут­рен­не­го поль­зо­ва­ния

   &nb= sp;[Browsable(true)] // по­ка­зы­вать в ди­зай­не­ре

   &nb= sp;[RefreshProperties(RefreshProperties.All)] // об­нов­лять ос­таль­ные свой­ст­ва при из­ме­не­нии это­го

   &nb= sp;public int Mi­ni­mum­Va­lue { // свой­ст­во (ви­ди­мое и в ди­зай­не­ре)

   &nb= sp;get { re­turn min­Val; }

   &nb= sp;set {

   &nb= sp;if (va­lue ›=3D max­Val) { throw new Ar­gu­men­tEx­cep­ti­on("mi­ni­mum v= a­lue must be ‹ ma­xi­mum"); } // про­ве­рить на мень­ше мак­си­му­ма

   &nb= sp;minVal =3D va­lue; // ус­та­но­вить зна­че­ние

   &nb= sp;if (Va­lue2 ‹=3D min­Val) { Va­lue2 =3D min­Val+1; } // поп­ра­вить зна­че­ния= пол­зун­ков ес­ли на­до

   &nb= sp;if (Va­lue1 ‹ min­Val) { Va­lue1 =3D min­Val; }

   &nb= sp;SetPos1(); // поп­ра­вить пря­мо­уголь­ник пол­зун­ка 1

   &nb= sp;SetPos2();// поп­ра­вить пря­мо­уголь­ник пол­зун­ка 2

   &nb= sp;}

   &nb= sp;}

    Аналогично ос­таль­ные по­ля:<= span style=3D'color:black'>

    

   &nb= sp;private int max­Val; // мак­си­мум по­лос­ки слай­де­ра

   &nb= sp;[Browsable(true)]

   &nb= sp;[RefreshProperties(RefreshProperties.All)]

   &nb= sp;public int Ma­xi­mum­Va­lue {

   &nb= sp;get { re­turn max­Val; }

   &nb= sp;set {

   &nb= sp;if (va­lue ‹=3D min­Val) { throw new Ar­gu­men­tEx­cep­ti­on("ma­xi­mum v= a­lue must be › mi­ni­mum"); }

   &nb= sp;maxVal =3D va­lue;

   &nb= sp;if (Va­lue1 ›=3D max­Val) { Va­lue1 =3D max­Val-1; }

   &nb= sp;if (Va­lue2 › max­Val) { Va­lue2 =3D max­Val; }

   &nb= sp;SetPos1();

   &nb= sp;SetPos2();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;private int val1; // зна­че­ние пол­зун­ка 1

   &nb= sp;[Browsable(true)]

   &nb= sp;public int Va­lue1 {

   &nb= sp;get { re­turn val1; }

   &nb= sp;set {

   &nb= sp;if (va­lue › max­Val) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue1 must be bet­we­en min and max va­lu­es"); } // мень­ше мак­си­му­ма=

   &nb= sp;if (va­lue ‹ min­Val) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue1 must be bet­we­en min and max va­lu­es"); } // боль­ше ми­ни­му­ма<= /span>

   &nb= sp;if (va­lue ›=3D Va­lue2) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue1 mus= t be ‹ Va­lue2"); } // мень­ше пол­зун­ка 2

   &nb= sp;val1 =3D va­lue; // ус­та­но­вить зна­че­ние

   &nb= sp;SetPos1(); // поп­ра­вить пря­мо­уголь­ник пол­зун­ка 1

   &nb= sp;}

   &nb= sp;}

   &nb= sp;private int val2; // ана­ло­гич­но пол­зу­нок 2

   &nb= sp;[Browsable(true)]

   &nb= sp;public int Va­lue2 {

   &nb= sp;get { re­turn val2; }

   &nb= sp;set {

   &nb= sp;if (va­lue › max­Val) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue2 must be bet­we­en min and max va­lu­es"); }

   &nb= sp;if (va­lue ‹ min­Val) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue2 must be bet­we­en min and max va­lu­es"); }

   &nb= sp;if (va­lue ‹=3D Va­lue1) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue2 mus= t be › Va­lue1"); }

   &nb= sp;OnValueChanged(new User­Con­t­rol1Va­lu­eC­han­ge­dE­ven­tAr­gs(val2, va­lue, 2));<= /span>

   &nb= sp;val2 =3D va­lue;

   &nb= sp;SetPos2();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;

    Теперь за­да­дим фун­к­ции оп­= ре­де­ле­ния пря­мо­уголь­ни­ков пол­зун­ков:

    

   &nb= sp;private vo­id Set­Pos1() {

   &nb= sp;slider1.X =3D (int)(((do­ub­le)val1 - min­Val) / (max­Val - min­Val) * (Width - 5));<= o:p>

   &nb= sp;}

   &nb= sp;private vo­id Set­Pos2() {

   &nb= sp;slider2.X =3D (int)(((do­ub­le)val2 - min­Val) / (max­Val - min­Val) * (Width - 5));<= o:p>

   &nb= sp;}

    

    И до­ба­вим ини­ци­али­за­цию = в кон­с­т­рук­тор:

    

   &nb= sp;public User­Con­t­rol1() {

   &nb= sp;InitializeComponent();

   &nb= sp;minVal =3D 0;

   &nb= sp;maxVal =3D 100;

   &nb= sp;val2 =3D 100;

   &nb= sp;slider1 =3D new Rec­tan­g­le(0, 2, 4, He­ight - 4);

   &nb= sp;slider2 =3D new Rec­tan­g­le(0, 2, 4, He­ight - 4);

   &nb= sp;SetPos1();

   &nb= sp;SetPos2();

   &nb= sp;}

    

    Поскольку по­ло­же­ние пол­зун= ­ков опи­ра­ет­ся еще и на раз­мер эле­мен­та уп­рав­ле­ния, до­ба­вим со­бы­тие= Si­zeC­han­ged и опи­шем его:

    

   &nb= sp;private vo­id User­Con­t­rol1_Si­zeC­han­ged(obj­ect sen­der, Even­tArgs e) {<= /o:p>

   &nb= sp;SetPos1();

   &nb= sp;SetPos2();

   &nb= sp;}

    

    Приступаем к "мы­ши­ным&q= uot; фун­к­ци­ям:

    Создаем со­бы­тия на­жа­тие мы= ш­ки, от­пус­ка­ние мыш­ки и дви­же­ние мыш­ки.

    Нажатие мыш­ки:

    

   &nb= sp;private vo­id User­Con­t­rol1_Mo­use­Down(obj­ect sen­der, Mo­use­Even­tArgs e) {

   &nb= sp;if (sli­der1.Con­ta­ins(e.Lo­ca­ti­on)) { // ес­ли на­жа­ли на пол­зу­нок 1

   &nb= sp;slider1drag =3D true; // ус­та­но­вить флаг пол­зу­нок 1 та­щат

   &nb= sp;Invalidate(slider1); // об­но­вить ри­сов­ку пол­зун­ка 1

   &nb= sp;}

   &nb= sp;else if (sli­der2.Con­ta­ins(e.Lo­ca­ti­on)) { // то же для пол­зун­ка 2

   &nb= sp;slider2drag =3D true;

   &nb= sp;Invalidate(slider2);

   &nb= sp;}

   &nb= sp;}

    

    Отпускание мыш­ки:

    

   &nb= sp;private vo­id User­Con­t­rol1_Mo­use­Up(obj­ect sen­der, Mo­use­Even­tArgs e) {

   &nb= sp;if (sli­der1drag) { // ес­ли та­щи­ли пол­зу­нок 1

   &nb= sp;slider1drag =3D fal­se; // снять вы­де­ле­ние

   &nb= sp;slider1hover =3D fal­se;

   &nb= sp;Invalidate(slider1); // пе­ре­ри­со­вать внут­рен­нюю часть

   &nb= sp;}

   &nb= sp;else if (sli­der2drag) { // то же для пол­зун­ка 2

   &nb= sp;slider2drag =3D fal­se;

   &nb= sp;slider2hover =3D fal­se;

   &nb= sp;Invalidate(slider2);

   &nb= sp;}

   &nb= sp;}

    

    Перемещение мыш­ки. Тут нам по= ­на­до­бит­ся воз­мож­ность об­нов­лять не толь­ко сам пол­зу­нок, но всю об­ласть от пре= ­ды­ду­ще­го по­ло­же­ния пол­зун­ка, до его те­ку­ще­го по­ло­же­ния. Ина­че бу­дут ос­= та­вать­ся по­лос­ки при быс­т­ром дви­же­нии. Для это­го соз­да­дим еще од­но по­ле R= ec­tan­g­le, и про­пи­шем его ини­ци­али­за­цию в кон­с­т­рук­тор:

    

   &nb= sp;private Rec­tan­g­le in­va­li­da­te­Rect;

   &nb= sp;public User­Con­t­rol1() {

   &nb= sp;...

   &nb= sp;invalidateRect =3D new Rec­tan­g­le();

   &nb= sp;}

    

    Вот те­перь фун­к­ция пе­ре­ме= ­ще­ния мыш­ки:

    

   &nb= sp;private vo­id User­Con­t­rol1_Mo­use­Mo­ve(obj­ect sen­der, Mo­use­Even­tArgs e) {<= o:p>

   &nb= sp;if (sli­der1drag) { // ес­ли пол­зу­нок 1 та­щат

   &nb= sp;if (e.X ›=3D 2 e.X ‹=3D Width - 3 e.X ‹ sli­der2.Left+1) { // про­ве­рить мож­= но ли сю­да дви­гать­ся

   &nb= sp;invalidateRect =3D sli­der1; // за­пом­ним ста­рое по­ло­же­ние пол­зун­ка

   &nb= sp;Value1 =3D (int)Math.Ro­und(min­Val + ((do­ub­le)(e.X - 2) / (Width - 5)) * (max­V= al - min­Val)); // ус­та­но­вим но­вое по­ло­же­ние по мыш­ке, что об­но­вит по­= ло­же­ние пол­зун­ка

   &nb= sp;invalidateRect =3D Rec­tan­g­le.Uni­on(sli­der1, in­va­li­da­te­Rect); // по­лу­чим пря­мо= ­уголь­ник объ­еди­ня­ющий ста­рое и но­вое по­ло­же­ние

   &nb= sp;Invalidate(Rectangle.Inflate(invalidateRect, 1, 1)); // об­но­вим пря­мо­уголь­ник и при­ле­га­ющие пик­се­ли=

   &nb= sp;}

   &nb= sp;}

   &nb= sp;else if (sli­der2drag) { // то же для пол­зун­ка 2

   &nb= sp;if (e.X ›=3D 2 e.X ‹=3D Width - 3 e.X › sli­der1.Right-1) {<= /i>

   &nb= sp;invalidateRect =3D sli­der2;

   &nb= sp;Value2 =3D (int)Math.Ro­und(min­Val + ((do­ub­le)(e.X - 2) / (Width - 5)) * (max­V= al - min­Val));

   &nb= sp;invalidateRect =3D Rec­tan­g­le.Uni­on(sli­der2, in­va­li­da­te­Rect);

   &nb= sp;Invalidate(Rectangle.Inflate(invalidateRect, 1, 1));

   &nb= sp;}

   &nb= sp;}

   &nb= sp;else { // ес­ли ни­ко­го не та­щат

   &nb= sp;if (sli­der1.Con­ta­ins(e.Lo­ca­ti­on)) { // ес­ли мыш­ка над пер­вым пол­зун­= ком

   &nb= sp;if (!sli­der1ho­ver) { // ес­ли флаг под­с­вет­ки не ус­та­нов­лен

   &nb= sp;slider1hover =3D true; // ус­та­но­вить

   &nb= sp;Invalidate(slider1); // пе­ре­ри­со­вать внут­рен­нюю часть пол­зун­ка

   &nb= sp;}

   &nb= sp;}

   &nb= sp;else if (sli­der2.Con­ta­ins(e.Lo­ca­ti­on)) { // то же для пол­зун­ка 2

   &nb= sp;//draw ho­ver sta­te

   &nb= sp;if (!sli­der2ho­ver) {

   &nb= sp;slider2hover =3D true;

   &nb= sp;Invalidate(slider2);

   &nb= sp;}

   &nb= sp;}

   &nb= sp;else { // ес­ли мыш­ка не над пол­зун­ка­ми

   &nb= sp;if (sli­der1ho­ver) { // ес­ли пол­зу­нок 1 был под­с­ве­чен=

   &nb= sp;slider1hover =3D fal­se; // снять под­с­вет­ку

   &nb= sp;Invalidate(slider1); // пе­ре­ри­со­вать внут­рен­нюю часть

   &nb= sp;}

   &nb= sp;else if (sli­der2ho­ver) { // то же для пол­зун­ка 2

   &nb= sp;slider2hover =3D fal­se;

   &nb= sp;Invalidate(slider2);

   &nb= sp;}

   &nb= sp;}

   &nb= sp;}

   &nb= sp;GC.Collect(); // очис­тить па­мять

   &nb= sp;}

   &nb= sp;

    Очистка па­мя­ти нуж­на, по­то= ­му как фун­к­ции Rec­tan­g­le.Uni­on и Rec­tan­g­le.Infla­te соз­да­ют но­вые = объ­ек­ты Rec­tan­g­le, ко­то­рые пос­ле окон­ча­ния фун­к­ции ухо­дят в му­сор.

    

    Добавим еще мо­ди­фи­ка­тор кл= ас­са, что­бы его бы­ло нор­маль­но вид­но в ди­зай­не­ре:

    

   &nb= sp;[DesignTimeVisible(true)]

   &nb= sp;public par­ti­al class User­Con­t­rol1 : User­Con­t­rol

   &nb= sp;{

    

    Вобщем-то эле­мент уп­рав­ле­н= ия ра­бо­та­ет. Мож­но соз­дать фор­му и по­са­дить на нее наш эле­мент, нас­т­ро­ить и пос= ­мот­реть как ра­бо­та­ет. Вот толь­ко не очень удоб­но - при из­ме­не­нии зна­че­ния= , он об этом не со­об­ща­ет. До­ба­вим ему со­бы­тие из­ме­не­ние зна­че­ния:

    

   &nb= sp;[Browsable(true)] // вид­но в ди­зай­не­ре

   &nb= sp;public event Va­lu­eC­han­ge­dE­ven­t­De­le­ga­te Va­lu­eC­han­ged =3D null; // со= ­бы­тие

   &nb= sp;public de­le­ga­te vo­id Va­lu­eC­han­ge­dE­ven­t­De­le­ga­te(obj­ect sen­der, Use= r­Con­t­rol1Va­lu­eC­han­ge­dE­ven­tArgs e); // де­ле­гат об­ра­бот­чи­ка со­бы­тия

   &nb= sp;protected vir­tu­al vo­id On­Va­lu­eC­han­ged(User­Con­t­rol1Va­lu­eC­han­ge­dE­ven­t= Args e) { // об­ра­бот­чик

   &nb= sp;if (Va­lu­eC­han­ged !=3D null) {

   &nb= sp;ValueChanged(this, e);

   &nb= sp;}

   &nb= sp;}

    

    Нам по­на­до­бит­ся еще класс = ар­гу­мен­тов со­бы­тия:

    

   &nb= sp;public class User­Con­t­rol1Va­lu­eC­han­ge­dE­ven­tArgs : Even­tArgs // ар­гу­мен= ­ты со­бы­тия

   &nb= sp;{

   &nb= sp;public User­Con­t­rol1Va­lu­eC­han­ge­dE­ven­tAr­gs(int inOl­d­Va­lue, int in­New­= Va­lue, byte in­Va­lue) { // кон­с­т­рук­тор

   &nb= sp;oldValue =3D inOl­d­Va­lue; // ста­рое зна­че­ние

   &nb= sp;newValue =3D in­New­Va­lue; // но­вое зна­че­ние

   &nb= sp;value =3D in­Va­lue; // ка­кой пол­зу­нок

   &nb= sp;}

   &nb= sp;public int ol­d­Va­lue;

   &nb= sp;public int new­Va­lue;

   &nb= sp;/// ‹sum­mary›

   &nb= sp;/// 1 - va­lue 1, 2 - va­lue 2

   &nb= sp;/// ‹/sum­mary›

   &nb= sp;public byte va­lue;

   &nb= sp;}

   &nb= sp;

    И вве­дем вы­зов со­бы­тия при= из­ме­не­нии зна­че­ний­:

   &nb= sp;public int Va­lue1 {

   &nb= sp;get { re­turn val1; }

   &nb= sp;set {

   &nb= sp;if (va­lue › max­Val) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue1 must be bet­we­en min and max va­lu­es"); }

   &nb= sp;if (va­lue ‹ min­Val) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue1 must be bet­we­en min and max va­lu­es"); }

   &nb= sp;if (va­lue ›=3D Va­lue2) { throw new Ar­gu­men­tEx­cep­ti­on("va­lue1 mus= t be ‹ Va­lue2"); }

   &nb= sp;OnValueChanged(new User­Con­t­rol1Va­lu­eC­han­ge­dE­ven­tAr­gs(val1, va­lue, 1)); // вы­зов с= о­бы­тия

   &nb= sp;val1 =3D va­lue;

   &nb= sp;SetPos1();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;

    Аналогичную строч­ку до­ба­вим= и для вто­ро­го зна­че­ния:

   &nb= sp;OnValueChanged(new User­Con­t­rol1Va­lu­eC­han­ge­dE­ven­tAr­gs(val2, va­lue, 2)); // вы­зов с= о­бы­тия

   &nb= sp;

    И пос­лед­ний штрих - ус­та­но= ­вим для клас­са со­бы­ти­ем по умол­ча­нию - на­ше со­бы­тие:

   &nb= sp;[DefaultEvent("ValueChanged")]

   &nb= sp;public par­ti­al class User­Con­t­rol1 : User­Con­t­rol

   &nb= sp;

    Вот и все. Мож­но ис­поль­зо­в= ать и в ди­зай­не­ре и во вре­мя ра­бо­ты прог­рам­мы. Сде­ла­ем Form1, до­ба­вим= в нее наш эле­мент уп­рав­ле­ния, сде­ла­ем па­ру тек­с­то­вых око­шек и про­= пи­шем для на­ше­го эле­мен­та уп­рав­ле­ния со­бы­тие Va­lu­eC­han­ged:

   &nb= sp;private vo­id user­Con­t­rol11_Va­lu­eC­han­ged(obj­ect sen­der, User­Con­t­rol1Va­= lu­eC­han­ge­dE­ven­tArgs e) {

   &nb= sp;if (e.va­lue =3D=3D 1) { // ес­ли пер­вый пол­зу­нок

   &nb= sp;textBox1.Text =3D e.new­Va­lue.ToS­t­ring(); // внес­ти но­вое зна­че­ние в тек­с­то­вое = по­ле 1

   &nb= sp;}

   &nb= sp;else if (e.va­lue =3D=3D 2) { // ана­ло­гич­но для пол­зун­ка 2

   &nb= sp;textBox2.Text =3D e.new­Va­lue.ToS­t­ring();

   &nb= sp;}

   &nb= sp;}

   &nb= sp;

    Да до­ба­вим в кон­с­т­рук­тор= фор­мы та­кие строч­ки:

   &nb= sp;textBox1.Text =3D user­Con­t­rol11.Va­lue1.ToS­t­ring(); // за­пи­сать на­чаль­ное зна­че= ­ние пол­зун­ка 1 в тек­с­то­вое по­ле 1

   &nb= sp;textBox2.Text =3D user­Con­t­rol11.Va­lue2.ToS­t­ring();// ана­ло­гич­но для пол­зун­ка 2=

   &nb= sp;

    Вот все и ра­бо­та­ет. Не иде­= аль­но, ко­неч­но. Но "де­ше­во, на­деж­но и прак­тич­но".

    Каталог про­ек­та для MS Vi­su= ­al Stu­dio 2005 с при­ме­ром на­хо­дит­ся в ар­хи­ве при­ме­ров, под­ка­та­лог user­con­t­rol2.

    Архив при­ме­ров мож­но ска­ча= ть здесь: http:/= /www.ro­bin­land.com/csharp-ba­sis/sam­p­les.zip.

        

Часть 4.

  &nb= sp; Основы GDI+.

Гла­ва 1. Ос­нов­ные по­ня­тия и ошиб­ки.

  &nb= sp; 

    

  &nb= sp; 

    Приступаю к рас­ска­зу как ри­= со­вать в .NET.

    Несколько ввод­ных слов.

    Если вы хо­ти­те что-то изоб­р= а­зить на эк­ра­не - вам на­до это что-то на­ри­со­вать. Дру­го­го спо­со­ба нет. = Все ок­на, кноп­ки и про­чие кон­т­ро­лы - это все ри­су­ет­ся, прос­то код ри­= со­ва­ния на­пи­сан за вас. Но те, кто соз­да­ют свои эле­мен­ты уп­рав­ле­ния зна­ют, что ри­со­вать при­хо­дит­ся все - на­чи­ная от рам­ки и за­кан­чи­вая вве­= ден­ным тек­с­том.

    Есть раз­ные тех­но­ло­гии ри­= со­ва­ния. Мож­но наз­вать три ос­нов­ные - GDI+, Di­rectX, сис­тем­ные фун­к­ции.

    Системные фун­к­ции име­ют оче= нь ог­ра­ни­чен­ные воз­мож­нос­ти, од­на­ко ра­бо­та­ют очень быс­т­ро. В слу­чае тех же кон­т= ­ро­лов, луч­ше поль­зо­вать­ся фун­к­ци­ями сис­те­мы для ри­со­ва­ния кус­ков сти­= ля, эти фун­к­ции ра­бо­та­ют нам­но­го быс­т­рее, чем GDI+, пос­коль­ку вмес­то дли­тель­но­го про­ри­со­вы­ва­ния кар­тин­ки, они ко­пи­ру­ют со­дер­жи­мое нап­ря­мую в ви­део па­мять.

    DirectX - са­мая быс­т­рая тех= ­но­ло­гия, пос­коль­ку ри­су­ет пря­мо в ви­део па­мя­ти, ис­поль­зуя гра­фи­чес­кие ч= и­пы-уско­ри­те­ли. Од­на­ко име­ет ряд не­дос­тат­ков - ко­ли­чес­т­во и объ­ем под­г­ру­жа­ем= ых биб­ли­отек и драй­ве­ров мо­жет пре­вы­шать объ­ем прог­рам­мы в нес­коль­= ко раз. Не го­во­ря уже о том, что прог­рам­ми­ро­вать под Di­rectX мно­го сло= ж­нее, чем под GDI+.

    GDI+ - са­мая мед­лен­ная тех­= но­ло­гия. И при этом са­мая удоб­ная для прог­рам­мис­та. В от­ли­чии от Di­rectX - р= и­су­ет ис­поль­зуя ос­нов­ной про­цес­сор, по­это­му за­ни­ма­ет ку­чу ре­сур­сов и вре­ме­ни. В от­ли­чии от сис­тем­ных фун­к­ций име­ет ог­ром­ные воз­мож­н= ос­ти. Го­во­рят, что в бли­жай­шем бу­ду­щем (в рай­оне .NET 4.0) GDI+ то­же бу­д= ет ри­со­вать ис­поль­зую гра­фи­чес­кие чи­пы...

    

    Основные мо­мен­ты GDI+=

    Все ри­со­ва­ние в GDI+ ве­дет= ­ся че­рез объ­ект клас­са Grap­hics. Это, мож­но ска­зать, яд­ро тех­но­ло­гии. Объ­е= кт со­дер­жит фун­к­ции ри­со­ва­ния плос­ких при­ми­ти­вов, изоб­ра­же­ний­, = тек­с­та, под­дер­жи­ва­ет прос­т­ран­с­т­вен­ные пре­об­ра­зо­ва­ния, уме­ет про­во­= дить сгла­жи­ва­ние в раз­ных ре­жи­мах и пр. Объ­ект Grap­hics мо­жет быть соз­= дан от лю­бо­го кон­т­ро­ла, вклю­чая фор­му, от лю­бо­го объ­ек­та Ima­ge, и е= ще нес­коль­ки­ми спо­со­ба­ми, ко­то­рые вряд ли по­на­до­бят­ся.

    Итак, ес­ли вам на­до что-то г= де-то на­ри­со­вать, дей­ст­ву­ете так:

    1. Соз­да­ете объ­ект Grap­hic= s, или по­лу­ча­ете уже соз­дан­ный­, от то­го объ­ек­та, на ко­то­ром вам на­до р= и­со­вать.

    2. Ри­су­ете че­рез объ­ект Gr= ap­hics.

    3. Уда­ля­ете объ­ект Grap­hic= s.

    

   &nb= sp;Graphics gr =3D Grap­hics.From­H­w­nd(pa­nel1.Han­d­le);

   &nb= sp;gr.DrawLine(pen1, po­int1, po­int2);

   &nb= sp;gr.FillEllipse(brush1, 0,0,100,100);

   &nb= sp;gr.DrawEllipse(pen2, 0,0,100,100);

   &nb= sp;gr.Dispose();

    

    

  &nb= sp; Если ва­шей прог­рам­ме нуж­но очень мно­го ри­со­вать, да еще из раз­ных фун­к­= ций­, вы мо­же­те сде­лать еди­ный объ­ект Grap­hics, и поль­зо­вать­ся им от раз= ­ных фун­к­ций. Впро­чем, это как пра­ви­ло пло­хая ме­то­ди­ка, го­раз­до луч­ш= е ис­поль­зо­вать род­ные event.

    

    Использование Grap­hics в e= vent.

    Обычно это выг­ля­дит так:

    

   &nb= sp;private vo­id pa­nel1_Pa­int(obj­ect sen­der, System.Win­dows.For­ms.Pa­in­tE­ven­t= Args e) {

   &nb= sp;e.Graphics.FillEllipse(Brushes.Magenta,0,0,150,150);

   &nb= sp;}

    

  &nb= sp; И луч­ше все­го, весь код ри­со­ва­ния зак­ла­ды­вать в event. Вам ник­то не = ме­ша­ет сде­лать его силь­но па­ра­мет­ри­чес­ким и пр. Мо­же­те в event пос­та­вит= ь вы­зов фун­к­ции соб­с­т­вен­но ри­со­ва­ния, и пе­ре­да­вать объ­ект e.Grap­hics = как ар­гу­мент, с мо­ди­фи­ка­то­ром ref. Это поз­во­лит вам ис­поль­зо­вать фу= н­к­цию ри­со­ва­ния не толь­ко для ри­со­ва­ния на эк­ра­не, но и для ри­со­ва­ния= на прин­те­ре, ес­ли у вас бу­дет под­дер­ж­ка пе­ча­ти, для ри­со­ва­ния на I= ma­ge, ес­ли вы бу­де­те сох­ра­нять изоб­ра­же­ние в файл, при­чем не тра­тя лиш­= них ре­сур­сов на на­пи­са­ния трех оди­на­ко­вых фун­к­ций­, или на соз­да­ние= но­вых объ­ек­тов Grap­hics и т.д.

    

    Основные при­емы

    Рисование ди­на­ми­чес­кой ин­= фор­ма­ции в фор­ме, обыч­но, про­из­во­дит­ся дву­мя пу­тя­ми - ли­бо в кон­т­ро­ле p= a­nel, ли­бо в Ima­ge кон­т­ро­ла pic­tu­re­Box. Ко­неч­но, вам ник­то не ме­ша­ет= ри­со­вать пря­мо в фор­ме, ес­ли у вас вся фор­ма от­ве­де­на под ри­со­ва­ние, но там свои заморочки. Мы рас­c­мот­рим схе­му ри­со­ва­ния че­рез event Pa­i= nt в двух кон­т­ро­лах.

    Общая схе­ма та­кая:

    1. Фун­к­ция pa­nel1_Pa­int ил= и pic­tu­re­Box1_Pa­int со­дер­жит па­ра­мет­ри­чес­кий код ри­со­ва­ния.

    2. Ос­таль­ные кон­т­ро­лы ме­= ня­ют па­ра­мет­ры ри­со­ва­ния.

    Предположим есть две ра­ди­ок­= ноп­ки - од­на за­да­ет крас­ный цвет, дру­гая си­ний. Тог­да код ри­со­ва­ния бу­= дет выг­ля­деть при­мер­но так:

    Для pa­nel:

    

   &nb= sp;private vo­id pa­nel1_Pa­int(obj­ect sen­der, System.Win­dows.For­ms.Pa­in­tE­ven­t= Args e) {

   &nb= sp;if (ra­di­oBut­ton1.Chec­ked) {

   &nb= sp;e.Graphics.FillEllipse(Brushes.Red,0,0,150,150);

   &nb= sp;}

   &nb= sp;else {

   &nb= sp;e.Graphics.FillEllipse(Brushes.Blue,0,0,150,150);<= /p>

   &nb= sp;}

   &nb= sp;}

    

    Для pic­tu­re­Box:

    

   &nb= sp;private vo­id pic­tu­re­Box1_Pa­int(obj­ect sen­der, System.Win­dows.For­ms.Pa­in­t= E­ven­tArgs e) {

   &nb= sp;Graphics gr =3D Grap­hics.Fro­mI­ma­ge(pic­tu­re­Box1.Ima­ge);=

   &nb= sp;gr.Clear(Color.White);

   &nb= sp;if (ra­di­oBut­ton1.Chec­ked) {

   &nb= sp;gr.FillEllipse(Brushes.Red,0,0,150,150);

   &nb= sp;}

   &nb= sp;else {

   &nb= sp;gr.FillEllipse(Brushes.Blue,0,0,150,150);

   &nb= sp;}

   &nb= sp;gr.Dispose();

   &nb= sp;}

    В пос­лед­нем слу­чае - gr.Cle= ­ar(Co­lor.Whi­te) - за­пол­ня­ет всю кар­тин­ку бе­лым цве­том, это нуж­но ес­ли на­до очис­т= ить то, что бы­ло на­ри­со­ва­но до это­го.

    В чем раз­ни­ца - ес­ли вам на= ­до ри­со­вать толь­ко в прог­рам­ме, ис­поль­зуй­те pa­nel, оно про­ще и мень­ше ре­сур­с= ов ест. Ес­ли ва­ша прог­рам­ма ра­бо­та­ет с изоб­ра­же­ни­ями, в том чис­ле с фай­ла­ми изоб­ра­же­ний - заг­руз­ка/сох­ра­не­ние, ри­суй­те в pic­tu­re­= Box.Ima­ge, так про­ще сох­ра­нять, во-пер­вых, и pic­tu­re­Box соз­дан для хра­не­ния = изоб­ра­же­ний­, так что де­шев­ле за­пи­сать Ima­ge в pic­tu­re­Box.Ima­ge, чем вы­зы­вать = в pa­nel1_Pa­int e.Grap­hics.Dra­wI­ma­ge().

    Но не за­бы­вай­те при заг­руз= ­ке фор­мы соз­да­вать Ima­ge в pic­tu­re­Box, ибо по умол­ча­нию, pic­tu­re­Bo= x.Ima­ge =3D null;

    Для ра­дио кно­пок:

    

   &nb= sp;private vo­id ra­di­oBut­ton1_Chec­ked­C­han­ged(obj­ect sen­der, Even­tArgs e) {

   &nb= sp;panel1.Refresh();

   &nb= sp;// или

   &nb= sp;pictureBox1.Refresh();

   &nb= sp;}

    

    Основные ошиб­ки

    Идеология GDI+ по­че­му-то у м= но­гих вы­зы­ва­ет ку­чу проб­лем... ско­рее все­го, по­то­му что лю­ди не по­ни­м= а­ют прин­цип ра­бо­ты Win­dows.

    Ошибка 1. Про­па­да­юще= е изоб­ра­же­ние - лю­ди ри­су­ют в pa­nel, или pic­tu­re­Box не в event Pa­int, а по на­жа­= тию кноп­ки, или от­ку­да-то еще и удив­ля­ют­ся, по­че­му их изоб­ра­же­ние не= пе­ре­ри­со­вы­ва­ет­ся, а про­па­да­ет, ког­да фор­ма пе­ре­ри­со­вы­ва­ет­ся (ее свер­ну­ли/раз­ве= р­ну­ли, уб­ра­ли за эк­ран/вы­ве­ли об­рат­но и пр.).

    Ответ 1. Так и дол­ж­но= быть - ког­да вы ри­су­ете по кон­т­ро­лу - вы ри­су­ете на эк­ра­не, ког­да эк­= ран пе­ре­ри­со­вы­ва­ет­ся, он пе­ре­ри­со­вы­ва­ет­ся с ну­ля, и ес­ли ваш ко= д ри­со­ва­ния не вклю­чен в це­поч­ку пе­ре­ри­со­вы­ва­ния - ва­ше­го ри­сун­ка и не бу­= дет. Что­бы вклю­чить ваш код в це­поч­ку - пос­тавь­те его в event Pa­int нуж­н= о­го кон­т­ро­ла.

    Ошибка 2. При ри­со­ва­= нии в кон­т­ро­ле pic­tu­re­Box, лю­ди ри­су­ют по кон­т­ро­лу, а не по Ima­ge.

    Ответ 2. Ког­да ри­су­е= те - по­лу­чай­те Grap­hics че­рез Grap­hics.Fro­mI­ma­ge(pic­tu­re­Box.Ima­ge),= a не e.Grap­hics.

    Ошибка 3. При ис­поль­з= о­ва­нии pic­tu­re­Box - соз­да­ет­ся но­вый Ima­ge, в нем про­во­дит­ся ри­со­ва­ни= е, по­том он встав­ля­ет­ся в pic­tu­re­Box.

    Ответ 3. На хре­на соз­= да­вать но­вый Ima­ge, каж­дый раз ког­да вы ри­су­ете. Соз­да­вать но­вый на­до то= ль­ко ког­да вы ме­ня­ете раз­мер pic­tu­re­Box.

    

    И пос­лед­нее, нес­мот­ря не н= е­дос­тат­ки тех­но­ло­гии сбо­ра му­со­ра (Gar­ba­ge Col­lec­tor), ре­ко­мен­ду­ет­ся им поль­зо­вать­ся. Во-пер­вых, ес­ли это вой­дет в при­выч­ку сей­час, то ког= ­да тех­но­ло­гия за­ра­бо­та­ет (нап­ри­мер, в .NET 4.0), вы бу­де­те пи­сать = пра­виль­но. Во-вто­рых, тех­но­ло­гия и сей­час ра­бо­та­ет с боль­ши­ми объ­ема­ми в п= а­мя­ти, а изоб­ра­же­ния (и мно­гие дру­гие объ­ек­ты ри­со­ва­ния) от­но­сят­ся им= ен­но к боль­шим объ­емам. Так что лю­бой соз­дан­ный ва­ми объ­ект Grap­hics дол= ­жен быть Dis­po­se() пос­ле окон­ча­ния ра­бо­ты с ним, лю­бой соз­дан­ный Ima­= ge дол­жен быть Dis­po­se() пос­ле окон­ча­ния ра­бо­ты с ним. И глав­ное, пос= ­ле всех Dis­po­se() - не за­бы­вай­те вы­зы­вать GC.Col­lect().

    

    Знающим ан­г­лий­ский мо­гу по= ­ре­ко­мен­до­вать сайт Bob Po­well (http://www.bob­po­w= ell.net/), как ис­точ­ник ку­чи по­лез­ной ин­фор­ма­ции для на­чи­на­ющих в GDI+.

    

Глава 2. Объект Graphics.

  &nb= sp; 

    Как уже го­во­ри­лось, Grap­hi= cs - ос­нов­ной объ­ект (и класс) для ри­со­ва­ния. Рас­смот­рим под­роб­но, что= он уме­ет.

    Конструкторы

    У клас­са есть ста­ти­чес­кие = фун­к­ции для соз­да­ния объ­ек­та Grap­hics, и об­ра­ти­те вни­ма­ние - нет pub­lic = кон­с­т­рук­то­ра. По­это­му соз­да­вать объ­ект при­хо­дить­ся од­ним из сле­ду­ющих спо­со­б= ов:

    FromHdc - по ука­за­те­= лю на кон­текст ус­т­рой­ст­ва (De­vi­ce Con­text) - ис­поль­зу­ет­ся ред­ко.

    FromHwnd - по ука­за­те= ­лю на кон­т­рол (или фор­му).

    

   &nb= sp;Graphics gr =3D Grap­hics.From­H­w­nd(pa­nel1.Han­d­le);

    

    FromImage - для ри­сун­= ка (Ima­ge).

    

   &nb= sp;Graphics gr =3D Grap­hics.Fro­mI­ma­ge(bit­map);

    

    Поле ри­со­ва­ния

    Объект Grap­hics при­вя­зан к = оп­ре­де­лен­но­му по­лю ри­со­ва­ния (Clip), ко­то­рое име­ет при­вяз­ку к объ­ек­ту, ко­ор­д= и­на­ты и раз­ме­ры. Очень удоб­ная вещь с точ­ки зре­ния двух мо­мен­тов - во-пер­= вых, вы мо­же­те из­ме­нять об­ласть, в ко­то­рой про­ис­хо­дит соб­с­т­вен­но р= и­со­ва­ние, не из­ме­няя ко­да ри­со­ва­ния. Нап­ри­мер, вам на­до ку­сок су­щес­т­ву­ю= щей кар­тин­ки пе­рек­ра­сить - вы мо­же­те зак­ра­сить этот ку­сок, ука­зав то= ч­но все его ко­ор­ди­на­ты, а мо­же­те за­лить всю кар­тин­ку че­рез Cle­ar, из= ­ме­нив раз­ме­ры и ко­ор­ди­на­ты Clip так, что­бы они сов­па­ли с нуж­ным вам кус= ­ком. И во-вто­рых, ри­со­ва­ние ве­дет­ся толь­ко в пре­де­лах Clip ре­ги­она, т= .е. на все, что ри­су­ет­ся вне его вре­мя и ре­сур­сы не тра­тят­ся.

    

    В клас­се есть на­бор фун­к­ци= й для уп­рав­ле­ния ко­ор­ди­на­та­ми и раз­ме­ра­ми Clip:

    свойство Clip - воз­в­р= а­ща­ет Re­gi­on в ко­то­ром ве­дет­ся ри­со­ва­ние, так­же поз­во­ля­ет ус­та­но­в= ить но­вый Re­gi­on нап­ря­мую.

    свойство Clip­Bo­unds -= воз­в­ра­ща­ет пря­мо­уголь­ник, опи­сы­ва­ющий ре­ги­он Clip.

    свойство Is­C­li­pEmpty= - по­ка­зы­ва­ет пуст ли Clip.

    свойство Is­Vi­sib­leC­li­p= Empty - то же, но с про­вер­кой ви­ди­мой час­ти Clip. Ра­бо­та­ет толь­ко при ри= ­со­ва­нии по кон­т­ро­лу.

    IsVisible - про­ве­ря­е= т, ви­дим ли дан­ный пря­мо­уголь­ник. Очень удоб­но, ес­ли вы ри­су­ете в кон­т­ро­ле что-то, что тре­бу­ет боль­ших и дол­гих рас­че­тов, преж­де чем счи­тать -= про­верь­те, а по­ка­за­но-то оно бу­дет.

    ExcludeClip - ис­к­лю­ч= а­ет из Clip фи­гу­ру, пе­ре­дан­ную как ар­гу­мент.

    IntersectClip - ос­тав­= ля­ет в Clip толь­ко об­ласть пе­ре­се­че­ния Clip и фи­гу­ры в ар­гу­мен­те.

    ResetClip - ус­та­нав­л= и­ва­ет Clip рав­ным бес­ко­неч­нос­ти.

    SetClip - ус­та­нав­ли­= ва­ет Clip из ар­гу­мен­тов.

    TranslateClip - cме­ща­= ет Clip по плос­кос­ти ри­со­ва­ния.

    

    Разница меж­ду Clip и ви­ди­мо= й час­тью Clip: ес­ли у вас есть pa­nel кон­т­рол, с вклю­чен­ным AutoS­c­roll и со­д= ер­жи­мое пре­вы­ша­ет раз­ме­ры pa­nel, то соз­да­вая Grap­hics по ука­за­те­лю на к= он­т­рол, вы по­лу­чи­те Clip =3D всей об­лас­ти pa­nel, од­на­ко ви­ди­мая часть Cli= p - это та, ко­то­рая по­ка­зы­ва­ет­ся в нас­то­ящий мо­мент поль­зо­ва­те­лю.= Но пом­ни­те, что ви­ди­мая часть Clip име­ет ко­ор­ди­на­ты и раз­мер кон­т­р= о­ла - т.е. в слу­чае pa­nel, со скрол­лом, сме­щен­ным впра­во до кон­ца, ви­ди= ­мый клип все рав­но бу­дет от 0;0 до pa­nel.Wid­th;pa­nel.He­ight.

    

    Рисование и за­лив­ка

    Среди фун­к­ций объ­ек­та мож­= но вы­де­лить две боль­шие груп­пы - для ри­со­ва­ния и для за­лив­ки.

    Почти все фун­к­ции, на­чи­на­= ющи­еся со сло­ва Draw - ри­су­ют за­дан­ную фи­гу­ру за­дан­ной руч­кой.

    Все фун­к­ции, на­чи­на­ющи­ес= я со сло­ва Fill - за­ли­ва­ют за­дан­ную фи­гу­ру за­дан­ной кис­тью.

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

    Список фун­к­ций весь­ма ве­ли= к:

    DrawArc - ри­су­ет ду­г= у

    DrawLine - ри­су­ет ли­= нию

    DrawPolygon - ри­су­ет = мно­го­уголь­ник и т.д.

    Аналогично, фун­к­ции Fil­l= Arc, Fil­lPol­y­gon и т.д.

    

    Дополнительное ри­со­ва­ние=

    Дополнительно к ри­со­ва­нию п= рос­тых форм есть сле­ду­ющие фун­к­ции:

    DrawString - ри­су­ет с= тро­ку, и за­ли­ва­ет ее за­дан­ной кис­тью.

    DrawIcon и Dra­wI­co­nUn­s­= t­ret­c­hed - ри­су­ет икон­ку (объект Icon), и ри­су­ет икон­ку без из­ме­не­ния, со­о= т­вет­с­т­вен­но.

    DrawImage, Dra­wI­ma­ge­Un­= s­ca­led и Dra­wI­ma­ge­Un­s­ca­le­dAn­d­C­lip­ped - ри­су­ет кар­тин­ку (объект= Ima­ge), ри­су­ет кар­тин­ку без из­ме­не­ний в ука­зан­ной точ­ке и ри­су­ет кар­ти= н­ку без из­ме­не­ний с об­рез­кой по ука­зан­но­му пря­мо­уголь­ни­ку со­от­вет= ­с­т­вен­но.

    Clear - за­ли­ва­ет все= по­ле ри­со­ва­ния ука­зан­ным цве­том.

    CopyFromScreen - ко­пи­= ру­ет, по­пик­сель­но, изоб­ра­же­ние на эк­ра­не в ука­зан­ном пря­мо­уголь­ни­ке= в ука­зан­ный пря­мо­уголь­ник по­ля ри­со­ва­ния.

    

    Текст

    Для ри­со­ва­ния тек­с­та есть= вспо­мо­га­тель­ные фун­к­ции:

    MeasureString - поз­во­= ля­ет по­лу­чить раз­ме­ры стро­ки, ког­да она бу­дет на­ри­со­ва­на.

    MeasureCharacterRanges = - поз­во­ля­ет по­лу­чить раз­ме­ры на­бо­ра сим­во­лов, ког­да они бу­дут на­ри­со­ва­ны.=

    Разница меж­ду фун­к­ци­ями в = раз­ном под­хо­де к оп­ре­де­ле­нию до­пус­ков на сви­са­ющие час­ти букв, раз­ный = до­пуск на сгла­жи­ва­ние и еще чуть-чуть. Под­с­чет в лю­бом слу­чае не иде­аль­ны= й­, так как фун­к­ции по­че­му-то не ис­поль­зу­ют ус­та­нов­лен­ный па­ра­метр= ти­па сгла­жи­ва­ния шриф­та в сис­те­ме и в объ­ек­те Grap­hics.

    свойство Tex­t­Ren­de­rin­g= ­Hint - оп­ре­де­ля­ет ре­жим сгла­жи­ва­ния тек­с­та. Ва­ри­ан­ты - без сгла­жи­= ва­ния (Sin­g­le­Bit­Per­Pi­xel) и со сгла­жи­ва­ни­ем (Anti­Ali­as), каж­дый мо­ж= ет быть с под­с­т­рой­кой сви­са­ющих час­тей (Grid­Fit) или без. Плюс есть Cl= e­ar­T­y­peG­rid­Fit - ри­су­ет че­рез дви­жок Cle­ar­T­y­pe. И есть ва­ри­ант System­De­fa­ult = - ис­поль­зо­вать нас­т­рой­ки сис­те­мы.

    свойство Tex­t­Con­t­rast - оп­ре­де­ля­ет кон­т­рас­т­ность тек­с­та, ес­ли в сис­те­ме вклю­че­но сгл= а­жи­ва­ние тек­с­та или Cle­ar­T­y­pe.

    

    Преобразования

    При ри­со­ва­нии до­воль­но ча= с­то не­об­хо­ди­мо про­вес­ти над изоб­ра­же­ни­ем, или бу­ду­щим изоб­ра­же­ни= ­ем, не­кие тран­с­фор­ма­ции - из­ме­нить мас­ш­таб, по­вер­нуть, мо­жет быть п= од­ви­нуть, не ме­няя ос­нов­но­го ко­да ри­со­ва­ния. Для это­го в GDI+ есть класс Mat= ­rix, опи­сы­ва­ющий век­тор­ные (ко­ор­ди­нат­ные) тран­с­фор­ма­ции для каж­дой= ри­су­емой точ­ки. Для тех кто не зна­ет, или уже бла­го­по­луч­но за­был что та­кое м= ат­ри­цы и как с ни­ми ра­бо­та­ют, есть фун­к­ции, ко­то­рые вы­пол­ня­ют опе­ра­ции над мат­ри­цей за вас.

    свойство Tran­s­form - = воз­в­ра­ща­ет и поз­во­ля­ет за­дать мат­ри­цу пре­об­ра­зо­ва­ний.

    MultiplyTransform - пе­= рем­но­жа­ет те­ку­щую мат­ри­цу и мат­ри­цу в ар­гу­мен­те в за­дан­ном по­ряд­ке.

    TranslateTransform - пе= ­ред­ви­га­ет ри­со­ва­ние по плос­кос­ти.

    RotateTransform - по­во= ­ра­чи­ва­ет ри­со­ва­ние от­но­си­тель­но на­ча­ла ко­ор­ди­нат.

    ResetTransform - об­ну­= ля­ет все тран­с­фор­ма­ции.

    ScaleTransform - из­ме­= ня­ет мас­ш­таб, по каж­дой оси от­дель­но.

    TransformPoints - пре­о= б­ра­зу­ет ко­ор­ди­на­ты за­дан­но­го мас­си­ва то­чек из од­ной сис­те­мы ко­ор­ди­н= ат в дру­гую.

    

    Качество

    Еще есть на­бор свойств, оп­ре= ­де­ля­ющих ка­чес­т­во ри­со­ва­ния.

    CompositingMode - оп­ре= ­де­ля­ет как ри­сун­ки (Ima­ge) бу­дут ри­со­вать­ся. Ва­ри­ан­ты So­ur­ce­Copy - цв= ет ри­сун­ка пе­рек­ры­ва­ет под­лож­ку, So­ur­ce­Over - цвет ри­сун­ка сме­ши= ­ва­ет­ся с цве­том под­лож­ки, в про­пор­ции, оп­ре­де­ля­емой аль­фа ком­по­нен­той= цве­та.

    CompositingQuality - оп= ­ре­де­ля­ет ка­чес­т­во пре­об­ра­зо­ва­ния ри­сун­ков (Ima­ge), ког­да они ри­су­ет­ся один по дру­го­му. Из ва­ри­ан­тов ре­аль­но поль­зо­вать­ся сто­ит толь­ко= Hig­h­Qu­ality - ка­чес­т­вен­но, но мед­лен­но и Hig­h­S­pe­ed - быс­т­ро, но не очень ка= ­чес­т­вен­но.

    DpiX и DpiY - по= з­во­ля­ют уз­нать dpi по обе­им осям.

    InterpolationMode - оп­= ре­де­ля­ет ме­тод ин­тер­по­ля­ции, по су­ти - сгла­жи­ва­ние, при ри­со­ва­нии. Ва­ри= ­ан­ты для ис­поль­зо­ва­ния в по­ряд­ке воз­рас­та­ния ка­чес­т­ва и вре­ме­ни: N= e­ares­t­Ne­ig­h­bor, Bi­li­ne­ar, Hig­h­Qu­alit­y­Bi­li­ne­ar, Bi­cu­bic, Hig­h­Qu­alit­y­Bi­cu­= bic.

    PixelOffsetMode - оп­ре= ­де­ля­ет ка­чес­т­во of­fset пик­се­лей­, чтоб я по­ни­мал что это та­кое :). Нас­ко= ль­ко я по­нял, это то­же па­ра­метр сгла­жи­ва­ния, но на уров­не сме­ше­ния цве= ­тов пик­се­лей. Для ис­поль­зо­ва­ния обыч­ные два ва­ри­ан­та - Hig­h­Qu­ality= и Hig­h­S­pe­ed, и ва­ри­ант No­ne - ни­ка­кой об­ра­бот­ки.

    SmoothingMode - оп­ре­д= е­ля­ет ре­жим сгла­жи­ва­ния ли­ний. Те же ва­ри­ан­ты - Hig­h­Qu­ality, Hig­h­S­p= e­ed и No­ne.

    

    Прочее

    Есть еще на­бор фун­к­ций­, ко= ­то­рые ни к ка­кой груп­пе не от­но­сять­ся, но иног­да они нуж­ны:

    GetNearestColor - воз­в= ­ра­ща­ет бли­жай­щий цвет к ар­гу­мен­ту в цве­то­вом прос­т­ран­с­т­ве объ­ек­та Gr= ap­hics.

    Save - поз­во­ля­ет сох= ­ра­нить сос­то­яние объ­ек­та Grap­hics: тран­с­фор­ма­ции, Clip, ка­чес­т­во.

    Restore - поз­во­ля­ет = во­ос­та­но­вить сос­то­янии объ­ект из ра­нее сох­ра­нен­но­го.

    

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

    

Глава 3. Цвета.

  &nb= sp; 

    Прежде чем на­чать ри­со­вать,= на­до бы ра­зоб­рать­ся с цве­та­ми и их ис­поль­зо­ва­ни­ем. Мы бу­дем рас­смат­= ри­вать толь­ко воп­ро­сы оп­ре­де­ле­ния и вы­бо­ра цве­тов в ком­пь­ютер­ных цве­= то­вых прос­т­ран­с­т­вах, и не бу­дем ка­сать­ся би­оло­го-ху­до­жес­т­вен­ных ас= ­пек­тов. Хо­тя есть нес­коль­ко мо­мен­тов, ко­то­рые не­об­хо­ди­мо знать и пом­нит= ь:

    1. вос­п­ри­ятие цве­тов у каж= ­до­го че­ло­ве­ка ин­ди­ви­ду­аль­но.

    2. Каж­дый мо­ни­тор/прин­тер/= и т.д. по­ка­зы­ва­ют один и тот же цвет (с точ­ки зре­ния цифр) по-раз­но­му. Бо­= лее то­го, боль­шин­с­т­во мо­ни­то­ров по­ка­зы­ва­ют один и тот же цвет по-ра= з­но­му в раз­ных час­тях эк­ра­на.

    

    Цветовые прос­т­ран­с­т­ва<= /b>

    Я ду­маю все зна­ют из­вес­т­н= ый пос­ту­лат - лю­бой цвет мож­но по­лу­чить сме­ше­ни­ем трех, так на­зы­ва­емых, ос­но= в­ных. Поп­рав­ка пер­вая: "лю­бой цвет" - это лю­бой из па­лит­ры, в= ос­п­ри­ни­ма­емой че­ло­ве­чес­ким гла­зом. Ус­то­яв­ше­еся мне­ние гла­сит, что че­ло­ве= ­чес­кий глаз раз­ли­ча­ет все­го око­ло 16 мил­ли­онов цве­тов. Су­щес­т­ву­ют мно­= гие не­сог­лас­ные с этим, но боль­шин­с­т­во ра­бо­та­ет имен­но с 2^24 цве­та= ­ми, и мы бу­дем рас­смат­ри­вать имен­но та­кие прос­т­ран­с­т­ва. Од­на­ко, на= ­до знать, что су­щес­т­ву­ют цве­то­вые прос­т­ран­с­т­ва пос­т­ро­ен­ные на 4= цве­тах (нап­ри­мер, CMYK) и на 6 цве­тах. Впро­чем, 4-х цвет­ные наш­ли при­ме­не­= ние толь­ко в по­лиг­ра­фии, а с 6-ти цвет­ны­ми я стал­ки­вал­ся толь­ко в те­= ории, и не знаю где они при­ме­ня­ют­ся.

    Итак, трех-осе­вые цве­то­вые = прос­т­ран­с­т­ва, са­мое из­вес­т­ное из них - RGB - Red (Крас­ный­) Gre­en (Зе­ле­ный­) Blue= (Си­ний­). Ис­поль­зу­ет­ся в ЭЛТ мо­ни­то­рах, во мно­гих прин­те­рах и во мно­гих фо= р­ма­тах фай­лов. Сме­ши­вая эти три ос­нов­ные цве­та в раз­ных про­пор­ци­ях мож­н= о по­лу­чить "лю­бой­" цвет. От­ве­дя по 8 бит на цвет мы по­лу­ча­ем 2^24 =3D 16777216 цве­тов, что, как при­ня­то, опи­сы­ва­ет все цве­та, вос­п­ри­ни­= ма­емые че­ло­ве­чес­ким гла­зом. Все до­воль­ны.

    Второе по по­пу­ляр­нос­ти про= с­т­ран­с­т­во - HSB (HSL/HSV) - Hue (Цвет­ность) Sa­tu­ra­ti­on (На­сы­щен­ность) Brig­h­= t­ness (Яркость) / Lig­h­t­ness (Осве­щен­ность) / Va­lue (Зна­че­ние). Ис­поль­зу= ­ет­ся прак­ти­чес­ки во всех гра­фи­чес­ких ре­дак­то­рах. Кста­ти, в стан­дар­т­= ном ди­ало­ге вы­бо­ра цве­та Win­dows ис­поль­зу­ет­ся имен­но это прос­т­ран­= с­т­во:

    

  &nb= sp; В дан­ном слу­чае все раз­ри­со­ва­но в фор­ме квад­ра­та, что не­вер­но. Hue= - это па­ра­метр, обоз­на­ча­ющий угол на цве­то­вом кру­ге. Справ­ка: цве= ­то­вой круг Ге­те, са­мый из­вес­т­ный­, при всей сво­ей пра­виль­нос­ти не был пр= и­нят в тех­но­ло­ги­чес­ком ми­ре. Од­на­ко, имен­но он пос­лу­жил ос­но­вой для= соз­да­ния прос­т­ран­с­т­ва HSB. Бо­лее пра­виль­ная фор­ма вы­бо­ра цве­та в про= с­т­ран­с­т­ве HSB та­кая:

    

  &nb= sp; C прос­т­ран­с­т­вом HSB свя­за­на нап­ри­ят­ная проб­ле­ма - па­ра­метр Hue = дол­жен при­ни­мать зна­че­ния от 0 до 360, что ни­как не ук­ла­ды­ва­ет­ся в нор­м= аль­ную дво­ич­ную сис­те­му за­пи­си дан­ных... Да и для Sa­tu­ra­ti­on и Brig­h­t= ­ness еди­но­го мне­ния нет - кто-то счи­та­ет что зна­че­ния дол­ж­ны быть от 0 = до 100, кто-то от 0 до 1, кто-то от 0 до 240... По­это­му, нес­мот­ря на то, ч= то ра­бо­тать мно­гие пред­по­чи­та­ют в нем, сох­ра­не­ние дан­ных ве­дет­ся в RGB, бла­го оба прос­т­ран­с­т­ва вза­имо­кон­вер­ти­ру­емые, хо­тя тут есть нес­коль­ко ло­ву­шек, об этом ни­же.

    Каждая ось лю­бо­го цве­то­во­= го прос­т­ран­с­т­ва так­же на­зы­ва­ет­ся ка­на­лом - крас­ный ка­нал, или ка= ­нал крас­но­го и т.п.

    Существует еще око­ло 10-15 цв= е­то­вых прос­т­ранств, но сре­ди Win­dows прог­рам­мис­тов они очень ма­ло рас­п­ро= с­т­ра­не­ны и мы их рас­смат­ри­вать не бу­дем.

    

    Битность цве­та

    Битность цве­та - па­ра­метр, = оп­ре­де­ля­ющий сколь­ко бит па­мя­ти при­хо­дит­ся на цвет каж­до­го пик­се­ля. Ес­ли вдруг кто не зна­ет: пик­сель - ми­ни­маль­ная еди­ни­ца пло­ща­ди эк­ра­на/рас­т= ­ро­во­го ри­сун­ка, т.е. точ­ка.

    Я уже упо­ми­нал, что на каж­д= ый из трех ос­нов­ных цве­тов вы­де­ли­ли по 8 бит и всем ста­ло хо­ро­шо. Од­на­= ко, это слу­чи­лось не так дав­но, а до это­го вы­де­лять по 3 бай­та на один п= ик­сель бы­ло не­поз­во­ли­тель­ной рос­кошью. Ис­то­рия раз­ви­тия при­мер­но та­к= ая:

    1. Мо­нох­ро­мы - один цвет. М= о­жет кто пом­нит, бы­ли та­кие мо­ни­то­ры, ко­то­рые по­ка­зы­ва­ли все ис­к­лю= ­чи­тель­но ядо­ви­то-зе­ле­ным цве­том.

    2. 4-х цвет­ные. Бы­ла та­кая = вещь, од­на­ко дол­го не про­жи­ла, пос­коль­ку ее быс­т­ро сме­ни­ли.

    3. 16-ти цвет­ные. Это уже на­= ча­ло нор­маль­но­го цве­та в ком­пь­юте­ре. На каж­дый пик­сель вы­де­ля­лось 4 = би­та, они опи­сы­ва­ли один из 16 из­вес­т­ных ком­пу цве­тов. Но, как вы­яс­ни­л= ось поз­же, это то­же бы­ло вре­мен­но - мо­ни­то­ры ста­ли по­ка­зы­вать все 16 мил­ли­онов, а ком­пы не мог­ли столь­ко вы­дать од­нов­ре­мен­но... И тог­= да при­ду­ма­ли вы­ход.

    4. 256 цве­тов. На каж­дый пик= ­сель вы­де­лял­ся байт па­мя­ти, ко­то­рый мог опи­сать цвет. Но тог­да же по­яв= и­лась идея па­литр - быйт па­мя­ти оп­ре­де­лял но­мер цве­та в па­лит­ре, а са­м= а па­лит­ра в 256 цве­тов вы­би­ра­лась из пол­но­го на­бо­ра.

    5. Даль­ше все прос­то - с рос= ­том ком­пь­ютер­ной мощ­нос­ти и объ­емов па­мя­ти по­явил­ся 16 бит­ный цвет - 65535 цве­тов, ни­ка­ких па­литр, и выг­ля­дит все впол­не прис­той­но. По­= том 24 би­та - 16 мил­ли­онов цве­тов, с ко­то­ры­ми сей­час все и ра­бо­та­ют.=

    6. 32 и 48 бит: 48 бит я по­ка= в де­ле не ви­дел, од­на­ко он есть, под не­го есть кар­ты и т.д. 32 би­та - име­ет= два при­ме­не­ния, во-пер­вых для 4-х цвет­ных прос­т­ранств, а во-вто­рых для = под­дер­ж­ки проз­рач­нос­ти, об этом чуть ни­же.

    

    Прозрачность

    Сначала проз­рач­ность по­яви­= лась в би­то­вой фор­ме - т.е. пик­сель или пол­нос­тью проз­рач­ный или цвет­ной. Проз­рач­ность в gif сде­ла­на имен­но так. По­том уже по­яви­лась гра­да­ц= и­он­ная проз­рач­ность. Для нее сде­ла­ли 256 гра­да­ций­, т.е. вы­де­ли­ли еще бай= т на хра­не­ние проз­рач­нос­ти пик­се­ля. Байт, хра­ня­щий проз­рач­ность по­лу= ­чил наз­ва­ние аль­фа. До­бав­ле­ние аль­фа ка­на­ла не соз­да­ет до­пол­ни­тел= ь­ной оси прос­т­ран­с­т­ва. Это не ком­по­нент цве­та в пря­мом смыс­ле, - это п= а­ра­метр, по­ка­зы­ва­ющий­, в ка­кой про­пор­ции на­до сме­ши­вать этот цвет, с ле­ж= а­щим "ни­же". Сре­ди рас­п­рос­т­ра­нен­ных фор­ма­тов фай­лов толь­ко= png под­дер­жи­ва­ет аль­фа-ка­нал.

    К сло­ву ска­зать, Win­dows до= сих пор уме­ет нор­маль­но ра­бо­тать толь­ко с би­то­вой проз­рач­нос­тью, го­= во­рят Vis­ta на­учи­лась ра­бо­тать с гра­да­ци­он­ной­, но это мы пос­мот­рим по= с­ле ре­ли­за.

    

    Форматы цве­тов

    Итак, сов­ме­щая вы­ше­опи­сан= ­ное в раз­ных ком­би­на­ци­ях мы по­лу­ча­ем фор­ма­ты цве­тов/цвет­нос­ти. Нап­р= и­мер, один из са­мых сей­час рас­п­рос­т­ра­нен­ных фор­ма­тов - 32bppARGB: 32 би= ­та на пик­сель, цве­то­вое прос­т­ран­с­т­во RGB с под­дер­ж­кой аль­фа-ка­на­= ла. Фор­ма­тов су­щес­т­ву­ет мно­жес­т­во, но все они по­нят­ны из наз­ва­ния,= я пе­ре­чис­лю и опи­шу толь­ко те, ко­то­рые ис­поль­зу­ют­ся в .NET 2.0 и с= о­дер­жать­ся в пе­ре­чис­ле­нии System.Dra­wing.Ima­ging.Pi­xel­For­mat:

    Alpha - каж­дый пик­сел= ь со­дер­жит толь­ко аль­фа-ка­нал. 8бит.

    Canonical - 32 би­та, A= RGB. Зна­че­ния в RGB ка­на­лах не из­ме­не­ны.

    DontCare - не ука­зы­ва= ть фор­мат.

    Extended - не ис­поль­з= у­ет­ся.

    Format16bppArgb1555 - 1= 6 бит на пик­сель, 1 бит на проз­рач­ность и по 5 бит на цве­то­вые ка­на­лы. Та­= ким об­ра­зом по­лу­ча­ем 32768 цве­тов и би­то­вую проз­рач­ность.

    Format16bppRgb555 - 16 = бит. по 5 бит на цве­то­вой ка­нал, 1 бит не ис­поль­зу­ет­ся. Те же 32768 цве­т= ов, но без проз­рач­нос­ти.

    Format16bppRgb565 - 16 = бит, по 5 бит на крас­ный и си­ний и 6 бит на зе­ле­ный. По­лу­ча­ем 65536 цве­т= ов.

    Format24bppRgb - 24 би­= та, по 8 на каж­дый цвет. Са­мый рас­п­рос­т­ра­нен­ный сей­час фор­мат без проз­р= ач­нос­ти.

    Format32bppArgb - 32 би= ­та, по 8 на каж­дый цвет и 8 на аль­фа-ка­нал. Са­мый рас­п­рос­т­ра­нен­ный се= й­час фор­мат с проз­рач­нос­тью.

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

    Format32bppRgb - 32 би­= та, по 8 на ка­нал и 8 не ис­поль­зу­ют­ся.

    Format48bppRgb - 48 бит= , по 16 на каж­дый ка­нал.

    Format64bppArgb - 64 би= ­та, по 16 на цве­то­вой ка­нал, и 16 на проз­рач­ность.

    Format64bppPArgb - то ж= е, что пре­ды­ду­щий­, но зна­че­ния в цве­то­вых ка­нал пре­об­ра­зо­ва­ны в со­о= т­вет­с­т­вии с аль­фа зна­че­ни­ем.

    

    Format1bppIndexed - ин­= дек­си­ро­ван­ные цве­та, т.е. за­вя­зан­ные на па­лит­ру. Па­лит­ра из двух цве­тов. 1 бит на пик­сель.

    Format4bppIndexed - 4 б= и­та, па­лит­ра из 16 цве­тов.

    Format8bppIndexed - 8 б= ит, па­лит­ра из 256 цве­тов.

    

    Format16bppGrayScale - = 65536 гра­да­ций се­ро­го.

    

    Лично я поль­зо­вал­ся толь­ко 24bppRGB, 32bppARGB, 8bppIn­de­xed и 16bppGray­S­ca­le. При соз­да­нии ма­с= ок в гра­фи­чес­ких ре­дак­то­рах час­то ис­поль­зу­ют од­но­би­то­вые фор­ма­ты, т.е. Al­p­ha впол­не мо­жет по­на­до­бить­ся. Ос­таль­ные, на мой взгляд, о= с­тав­ле­ны для сов­мес­ти­мос­ти со ста­ры­ми фор­ма­та­ми и с бу­ду­щи­ми фор­ма­та­м= и.

    

    Использование цве­тов в .NE= T

    Для ра­бо­ты с цве­та­ми есть = класс Co­lor. Он со­дер­жит на­бор ста­ти­чес­ких свойств, ко­то­рые оп­ре­де­ля­= ют рас­п­рос­т­ра­нен­ные цве­та. Нап­ри­мер, ес­ли вам ну­жен крас­ный цвет, = про­ще все­го по­лу­чить его так:

    

   &nb= sp;Color крас­ный =3D Co­lor.Red;

    

    Для нес­тан­дар­т­ных цве­тов = есть фун­к­ция Fro­mArgb():

    

   &nb= sp;Color стран­ный­Цвет =3D Co­lor.Fro­mArgb(255, 120, 12, 211);

    

    Есть еще две ста­ти­чес­кие фу= н­к­ции для соз­да­ния цве­та:

    FromKnownColor - соз­да= ­ет цвет из спис­ка из­вес­т­ных цве­тов, т.е. из пе­ре­чис­ле­ния Know­n­Co­lo= r, в ко­то­рое вхо­дят все стан­дар­т­ные цве­та и все сис­тем­ные цве­та.

    FromName - соз­да­ет цв= ет из стро­ки с име­нем цве­та.

    Помимо пе­ре­чис­ле­ния Know­C= o­lor есть еще пе­ре­чис­ле­ние System­Co­lors, ко­то­рое со­дер­жит толь­ко сис­= тем­ные цве­та - цве­та ра­мок, кно­пок, об­лас­ти ок­на и пр.

    

    У клас­са Co­lor есть еще не с= та­ти­чес­кие чле­ны:

    свойства A, R, G, B - воз­в­ра= ­ща­ют со­от­вет­с­т­ву­ющую ком­по­нен­ту цве­та.

    свойства Is­K­now­n­Co­lor, Is= ­Na­med­Co­lor, Is­S­y­s­tem­Co­lor - про­ве­ря­ют, яв­ля­ет­ся ли цвет "извес­т­ным&q= uot;, наз­ван­ным и сис­тем­ным со­от­вет­с­т­вен­но.

    свойство Na­me - воз­в­ра­ща­е= т имя цве­та

    ToKnownColor - воз­в­ра­ща­ет = член пе­ре­чис­ле­ния Know­n­Co­lor.

    GetHue, Get­Sa­tu­ra­ti­on, Ge= t­B­rig­h­t­ness - воз­в­ра­ща­ют зна­че­ния цве­та для осей Hue, Sa­tu­ra­ti­on и Brig­h­t­= ness прос­т­ран­с­т­ва HSB.

    свойство IsEmpty - про­ве­ря­е= т был ли цвет ини­ци­али­зи­ро­ван.

    ToArgb - воз­в­ра­ща­ет Int32.=

    

    Использование прос­т­ран­с­= т­ва HSB

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