Catone.

Азы C#.

СОДЕРЖАНИЕ
    
Часть 1.
Гла­ва 2. Син­так­сис дек­ла­ра­ций.
Гла­ва 3. Син­так­сис дек­ла­ра­ций мас­си­вов, свойств, де­ле­га­тов. As­sembly.
Гла­ва 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. Как читать чужой код.
    
    По прось­бам чи­та­те­лей­, по­пы­тал­ся на­пи­сать об ос­но­вах язы­ка. Впро­чем, толь­ко прис­ту­пив по­нял, что де­ло гиб­лое - что­бы пи­сать об ос­но­вах на­до быть или МУД­рым АКа­де­ми­кОМ, или дей­ст­ви­тель­но иметь та­лант пре­по­да­ва­те­ля. Я та­ко­во­го та­лан­та не имею. По­это­му смо­гу толь­ко крат­ко рас­ска­зать о не­ко­то­рых осо­бен­нос­тях. По­няв, что от та­ко­го тол­ку ма­ло, я ре­шил на­пи­сать для на­ча­ла как чи­тать чу­жой код. Ибо, нас­коль­ко я по­ни­маю, ос­нов­ные проб­ле­мы с по­ни­ма­ни­ем ко­да кро­ют­ся не в нез­на­нии, а в не­уме­нии эти са­мые зна­ния до­быть. В этой об­лас­ти пи­сать про­ще, т.к. мне час­то при­хо­дит­ся чи­тать чу­жой код, са­мо­го раз­но­го уров­ня и на са­мых раз­ных, в том чис­ле не­из­вес­т­ных мне, язы­ках.
    
    Кратко о язы­ке
    Итак язык C# вы­рос из C++, сох­ра­нив ос­нов­ные осо­бен­нос­ти син­так­си­са и опе­ра­то­ры. Да и ос­нов­ные опе­ра­то­ры оди­на­ко­вы во всех язы­ках, так что ес­ли вы зна­ете хоть один язык, то у вас не дол­ж­но воз­ни­кать воп­ро­са что та­кое for, if...then...else, do...whi­le и пр. Не са­мый рас­п­рос­т­ра­нен­ный опе­ра­тор ис­поль­зу­ющий­ся в C# - fo­re­ach. Соб­с­т­вен­но де­ла­ет то, что обоз­на­ча­ет - "for each = для каж­до­го", т.е. про­во­дит цикл для каж­до­го чле­на из мас­си­ва/кол­лек­ции/спис­ка.
    Особенностью всех С-по­доб­ных язы­ков яв­ля­ет­ся ог­ра­ни­чи­ва­ние бло­ков ко­да фи­гур­ны­ми скоб­ка­ми, в от­ли­чии от, нап­ри­мер, 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 соз­дан - он рас­сы­ла­ет­ся всем за­ин­те­ре­со­ва­ным ли­цам :), сво­его ро­да рас­сыл­ка по ин­те­ре­сам. Ва­ша прог­рам­ма, ес­ли она хо­чет по­лу­чать ин­фор­ма­цию о ка­ких-то со­бы­ти­ях - дол­ж­на под­пи­сать­ся на них. Все до­воль­но прос­то:
    Если вам на­до что­бы ва­ша прог­рам­ма по­лу­ча­ла ин­фор­ма­цию о со­бы­тии на­жа­тия кноп­ки - под­пи­ши­тесь:
    
    кнопка1.Click += new Even­t­Han­d­ler(на­жа­лиК­ноп­ку);
    Этой строч­кой вы вклю­ча­ете фун­к­цию "на­жа­лиК­ноп­ку" в спи­сок рас­сыл­ки ин­фор­ма­ции о со­бы­тии "Click" (на­жа­тие) для кноп­ки "кноп­ка1". Пом­нить на­до толь­ко об од­ном - лю­бые вла­дель­цы рас­сы­лок лю­ди хит­рые, кон­ку­рен­тов не лю­бят и рас­сы­ла­ют толь­ко "сво­им", что в пе­ре­во­де на прог­рам­ми­ро­ва­ние зна­чит - фун­к­ция, ко­то­рую вы вно­си­те в спи­сок дол­ж­на быть имен­но та­ко­го ви­да, ка­ко­го ждут в спис­ке, ина­че не при­мут. Пос­лед­ний мо­мент - Even­t­Han­d­ler - это пос­ред­ник меж­ду спис­ком рас­сыл­ки и по­лу­ча­те­лем, имен­но он дер­жит имя фун­к­ции, ко­то­рой на­до со­об­щить о со­бы­тии. Even­t­Han­d­ler'ов столь­ко же, сколь­ко и раз­ных event'ов, и он то­же дол­жен быть имен­но то­го ти­па, ко­то­рый ждут.
    
    Как чи­тать чу­жой код
    Конечно код бы­ва­ет раз­ный­, и что­бы чи­тать чу­жой код, ко­то­рый на­пи­сан на сла­боз­на­ко­мом язы­ке, без ком­мен­та­ри­ев и в неп­ри­выч­ной ма­не­ре нуж­но не толь­ко уме­ние, но и из­ряд­ная до­ля уда­чи и ин­ту­иции. Од­на­ко ес­ли код до­воль­но прос­той и на­пи­сан с ком­мен­та­ри­ями, хо­тя бы с ка­ки­ми-то, то про­чи­тать его проб­лем обыч­но не сос­тав­ля­ет. Боль­шой плюс C# в этом от­но­ше­нии, это воз­мож­ность вбить не­по­нят­ную строч­ку в Go­og­le или MSDN и поч­ти на­вер­ня­ка най­дет­ся при­мер с под­роб­ным опи­са­ни­ем, или что-то по­доб­ное. Боль­ше чем про C#, сре­ди .NET язы­ков, в се­ти толь­ко про VB на­пи­са­но.
    Итак, раз­бор ко­да. Рас­смот­рим код внут­ри фун­к­ции, так как все ос­таль­ное обыч­но воп­ро­сов не вы­зы­ва­ет.
    Код, как при­ви­ло, струк­ту­ри­ро­ван - или фор­ма­ти­ро­ван та­бу­ля­ци­ей­, или как-то ина­че (нап­ри­мер re­gi­on'ами). Очень час­то каж­дый блок име­ет ком­мен­та­рий­, опи­сы­ва­ющий про­из­во­ди­мую внут­ри опе­ра­цию - нап­ри­мер, "Чте­ние фай­ла", "За­пол­не­ние таб­ли­цы", "Под­бор раз­ме­ров". Та­ким об­ра­зом, мы зна­ет что там про­ис­хо­дит, ос­та­лось по­нять как. Боль­шин­с­т­во, т.е. поч­ти все, фун­к­ции и свой­ст­ва стан­дар­т­ных клас­сов об­ла­да­ют наз­ва­ни­ями точ­но опи­сы­ва­ющи­ми их дей­ст­вия. Ес­ли вы не зна­ете ан­г­лий­ский - поп­ро­буй­те сна­ча­ла пе­ре­вес­ти наз­ва­ние фун­к­ции, раз­бив его на от­дель­ные сло­ва по боль­шим бук­вам: Get­C­hil­d­F­rom­Po­int = Get Child From Po­int = По­лу­чить Ре­бен­ка Из Точ­ки - по­лу­чить до­чер­ний эле­мент уп­рав­ле­ния, на­хо­дя­щий­ся в точ­ке. Дру­гой ва­ри­ант - пе­ре­во­дить имя клас­са и фун­к­цию: Con­vert.ToS­t­ring = Con­vert To String = Пе­ре­вес­ти В Стро­ку. Не­ко­то­рые тер­ми­ны, ко­неч­но, при­дет­ся по­ис­кать в се­ти и пос­ле пе­ре­во­да. А не­ко­то­рые пе­ре­вес­ти по­нят­но не по­лу­чит­ся, по­это­му при­дет­ся по­нять, что имен­но фун­к­ция де­ла­ет. Нап­ри­мер, в та­кой вот строч­ке:
    
    largerImagesComboBox.DataSource = 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 - по­лу­чить име­на, воз­в­ра­ща­ет она мас­сив строк, ко­то­рый и за­пол­нит вы­па­да­ющее ок­но зна­че­ни­ями.
    
    Подведем ито­ги - ни­че­го тол­ком по­лез­но­го я не ска­зал. Все мои со­ве­ты мож­но свес­ти к трем фра­зам:
    1. вни­ма­тель­но чи­тай­те код
    2. учи­те ан­г­лий­ский­, кто не зна­ет
    3. поль­зуй­тесь справ­кой (MSDN) и Go­og­le'ом.
    
Гла­ва 2. Син­так­сис дек­ла­ра­ций.
    
    Вторая по­пыт­ка на­пи­сать что-то по­лез­ное для сов­сем на­чи­на­ющих в С#. 
    Чуть-чуть о струк­ту­ре ко­да: вер­х­ним эле­мен­том струк­ту­ры яв­ля­ют­ся прос­т­ран­с­т­ва имен (na­mes­pa­ce), до­воль­но фик­тив­ная вешь, приз­ван­ная упо­ря­до­чить ту ку­чу клас­сов, спис­ков, ин­тер­фей­сов и кон­с­тант, ко­то­рые уже су­щес­т­ву­ют и бу­дут соз­да­вать­ся. В na­mes­pa­ce мо­гут вхо­дить дру­гие na­mes­pa­ce, клас­сы, enum, кон­с­тан­ты и ин­тер­фей­сы. Класс сос­то­ит из фун­к­ций­, пе­ре­мен­ных и кон­с­тант. По по­во­ду пе­ре­мен­ных - они мо­гут быть объ­яв­ле­ны на лю­бом уров­не, и су­щес­т­во­вать бу­дут толь­ко в пре­де­лах (и во вре­мя жиз­ни) то­го бло­ка, в ко­то­ром объ­яв­ле­ны. Нап­ри­мер: пе­ре­мен­ная объ­яв­лен­ная в клас­се дос­туп­на для все­го клас­са, час­то для дру­гих клас­сов, жи­вет все вре­мя, по­ка жив класс (если он ста­тич­ный­) или объ­ект клас­са (если не ста­тич­ный­). Дру­гой при­мер: пе­ре­мен­ная объ­яв­лен­ная в пре­де­лах бло­ка if { }, ко­то­рый на­хо­дит­ся внут­ри бло­ка for { }, ко­то­рый на­хо­дит­ся внут­ри фун­к­ции клас­са... та­кая пе­ре­мен­ная бу­дет дос­туп­на толь­ко в пре­де­лах бло­ка if {}, в ко­то­ром объ­яв­ле­на и бу­дет жить по­ка не за­кон­чит­ся вы­пол­не­ние бло­ка, т.е. да­же до кон­ца фун­к­ции не до­жи­вет.
    О том, что та­кое ста­ти­чес­кий класс, что зна­чит пе­ре­мен­ная объ­яв­ле­на и пр - чи­тай­те ни­же.
    
    Синтаксис дек­ла­ра­ций
    Декларации - или объ­яв­ле­ния - это оп­ре­де­ле­ние име­ни и фор­ми­ро­ва­ние ти­па, прик­реп­ля­емо­го к это­му име­ни. Т.е. ес­ли вам нуж­но мес­то для хра­не­ния це­ло­го чис­ла, вы дол­ж­ны объ­явить пе­ре­мен­ную, с по­нят­ным вам име­нем, ти­па дан­ных це­лое (int). К сло­ву, int = in­te­ger = це­лое чис­ло. И пом­ни­те, дек­ла­ра­ция не соз­да­ет объ­ект и не вы­де­ля­ет па­мять - это толь­ко зак­реп­ле­ние име­ни за ти­пом. Для соз­да­ния дек­ла­ри­ро­ван­но­го объ­ек­та не­об­хо­ди­мо его ини­ци­али­зи­ро­вать (или оп­ре­де­лить), т.е. ли­бо прис­во­ить на­чаль­ное зна­че­ние, ли­бо за­пус­тить кон­с­т­рук­тор.
    Декларация клас­сов, фун­к­ций и пе­ре­мен­ных на уров­не клас­са тре­бу­ет ука­за­ния мо­ди­фи­ка­то­ра дос­ту­па. Впро­чем, ес­ли не ука­зы­вать бу­дет ис­поль­зо­ван стан­дар­т­ный­, обыч­но pri­va­te. Мо­ди­фи­ка­то­ры дос­ту­па - это клю­че­вые сло­ва, оп­ре­де­ля­ющие, от­ку­да мож­но бу­дет по­лу­чить дос­туп к дек­ла­ри­ро­ван­ной пе­ре­мен­ной­/фун­к­ции и пр. Их все­го нес­коль­ко:
    private - дос­туп воз­мо­жен толь­ко из­нут­ри клас­са, в ко­то­ром объ­яв­ле­на.
    protected - дос­туп воз­мо­жен из это­го клас­са и всех нас­лед­ни­ков.
    internal - дос­туп воз­мо­жен из всей as­sembly (сбор­ки/биб­ли­оте­ки, фай­ла ко­ро­че). Так­же при­ме­ня­ет­ся для клас­сов, enum и ин­тер­фей­сов.
    public - дос­туп воз­мо­жен от­ку­да угод­но. Так­же при­ме­ня­ет­ся для клас­сов, enum и ин­тер­фей­сов.
    Есть еще мо­ди­фи­ка­то­ры сос­то­яния, оп­ре­де­ля­ющие прин­ци­пи­аль­ное сос­то­яние фун­к­ций­:
    static - фун­к­ция ста­тич­на, т.е. воз­мож­но ее ис­пол­не­ние без соз­да­ния объ­ек­та клас­са. Нап­ри­мер, фун­к­ции клас­са System.Math - ста­тич­ные, пос­коль­ку вы­пол­ня­ют­ся прос­тым вы­зо­вом, без соз­да­ния объ­ек­та клас­са Math. Так­же при­ме­ня­ет­ся для клас­сов, тог­да все вхо­дя­щие в не­го фун­к­ции и пе­ре­мен­ные дол­ж­ны быть sta­tic.
    abstract - фун­к­ция толь­ко дек­ла­ри­ро­ва­на, а оп­ре­де­ле­ние фун­к­ции дол­ж­но быть в клас­сах нас­лед­ни­ках. Ис­поль­зу­ет­ся для соз­да­ния шаб­ло­на клас­са, все нас­лед­ни­ки ко­то­ро­го обя­за­тель­но име­ют не­кий на­бор фун­к­ций. Мо­жет су­щес­т­во­вать толь­ко в ab­s­t­ract клас­се. Так­же при­ме­ня­ет­ся для клас­сов.
    virtual - фун­к­цию мож­но пе­ре­оп­ре­де­лить в клас­се нас­лед­ни­ке.
    override - ис­поль­зу­ет­ся для ука­за­ния, что опи­сы­ва­емая фун­к­ция пе­ре­оп­ре­де­ля­ет ро­ди­тель­с­кую.
    Модификатор дос­ту­па для фун­к­ции/пе­ре­мен­ной внут­ри клас­са не мо­жет быть бо­лее дос­туп­ным, не­же­ли мо­ди­фи­ка­тор дос­ту­па все­го клас­са.
    Переменные, дек­ла­ри­ру­емые внут­ри фун­к­ций мо­ди­фи­ка­то­ра дос­ту­па не тре­бу­ют.
    
    Декларация пе­ре­мен­ных обя­за­тель­но сос­то­ит из ти­па дан­ных и име­ни, ос­таль­ное - по си­ту­ации и же­ла­нию.
    Примеры дек­ла­ра­ций пе­ре­мен­ных:
    
    int i;
    public do­ub­le myDo­ub­le1, myDo­ub­le2;
    internal sta­tic System.String con­st_string = "кон­с­тан­та";
    
    Декларация фун­к­ций обя­за­тель­но сос­то­ит из ти­па воз­в­ра­ща­емых дан­ных, име­ни и спис­ка ар­гу­мен­тов, ос­таль­ное - по си­ту­ации. Воз­ра­ща­емым ти­пом дан­ных мо­жет быть vo­id - пус­той - т.е. нет воз­в­ра­ща­емых дан­ных. Спи­сок ар­гу­мен­тов то­же мо­жет быть пус­тым. Фун­к­ции, в от­ли­чии от пе­ре­мен­ных, не мо­гут быть толь­ко дек­ла­ри­ро­ва­ны - они обя­за­тель­но сра­зу оп­ре­де­ля­ют­ся. Един­с­т­вен­ное ис­к­лю­че­ние - тип фун­к­ций ab­s­t­ract, они толь­ко дек­ла­ри­ру­ют­ся, а оп­ре­де­ля­ют­ся уже в клас­сах нас­лед­ни­ках.
    Примеры дек­ла­ра­ций и оп­ре­де­ле­ния фун­к­ций­:
    
    private vo­id MyFunc1(int i1, do­ub­le d1) {}
    internal ab­s­t­ract string Ge­tAs­S­t­ring();
    internal over­ri­de string Ge­tAs­S­t­ring() {}
    public sta­tic do­ub­le Get­Sum(do­ub­le d1, do­ub­le d2) { re­turn d1+d2; }
    
    Декларация клас­са мо­жет вклю­чать ука­за­ние клас­са ро­ди­те­ля, че­рез дво­ето­чие, да­лее че­рез за­пя­тую - под­к­лю­чен­ные ин­тер­фей­сы. Ин­тер­фей­с - спи­сок фун­к­ций. В ка­ком-то кри­вом смыс­ле - ро­ди­тель­с­кий класс, ко­то­рый мож­но про­из­воль­но при­ле­пить ку­да угод­но. Та­ким об­ра­зом по­лу­ча­ют­ся клас­сы, от раз­ных ро­ди­те­лей­, но под­дер­жи­ва­ющие од­ни и те же опе­ра­ции. До­воль­но удоб­ная вещь.
    Пример дек­ла­ра­ции клас­са:
    
    internal class MyClass1 : MyPa­ren­t­C­lass1, IIn­ter­fa­ce1, IIn­ter­fa­ce2 {
    }
    
    Декларация кон­с­т­рук­то­ра клас­са мо­жет вклю­чать ука­за­ние на дру­гой кон­с­т­рук­тор, ко­то­рый дол­жен быть за­пу­щен до дек­ла­ри­ру­емо­го, нап­ри­мер, кон­с­т­рук­тор ро­ди­тель­с­ко­го клас­са:
    
    public class MyClass : Pa­ren­t­C­lass {
    public MyClass(int i1) : ba­se() {
    }
    public MyClass() : this(0) {
    }
    }
    
    И пос­лед­нее - дек­ла­ра­ция Enum. Enum = enu­me­ra­ti­on = пе­ре­чис­ле­ние. Спи­сок зна­че­ний. Соз­да­ет­ся для двух це­лей­: его мож­но ис­поль­зо­вать прог­рам­мис­ту - свя­зать спи­сок це­ло­чис­лен­ных зна­че­ний с по­нят­ны­ми сло­ва­ми, и поль­зо­вать­ся сло­ва­ми, а не чис­ла­ми, в ко­то­рых лег­ко за­пу­тать­ся; его мож­но ис­поль­зо­вать поль­зо­ва­те­лям - име­на зна­че­ний в спис­ке мож­но лег­ко вы­вес­ти на поль­зо­ва­те­ля.
    Пример дек­ла­ра­ции Enum:
    
    public enum Ima­ge­Fi­le­For­mat {
    JPEG,
    PNG,
    TIFF,
    Bitmap}
    public enum Sup­por­ted­Lan­gu­ages {
    Русский=1,
    English=10,
    German=11}
    
Гла­ва 3. Син­так­сис дек­ла­ра­ций мас­си­вов, свойств, де­ле­га­тов. As­sembly.
    
    
    Массивы
    Итак, мас­сив - на­бор объ­ек­тов од­но­го ти­па, име­ющий чет­кую пос­ле­до­ва­тель­ность этих объ­ек­тов и стро­го за­дан­ный раз­мер, т.е. ко­ли­чес­т­во объ­ек­тов в на­бо­ре. Мас­сив мо­жет быть лю­бо­го ти­па, раз­мер мас­си­ва дол­жен быть боль­ше 0 и мень­ше чем мак­си­маль­ное зна­че­ние int (на 32 раз­ряд­ных ма­ши­нах - 2^32=4294967296).
    Декларация мас­си­ва по­доб­на дек­ла­ра­ции пе­ре­мен­ной­, с од­ним толь­ко из­ме­не­ни­ем - пос­ле ти­па дек­ла­ри­ру­емо­го мас­си­ва до­бав­ля­ют­ся квад­рат­ные скоб­ки - [].
    Пример:
    
    int[] in­tAr­ray;
    В строч­ке вы­ше по­ка­за­на дек­ла­ра­ция од­но­мер­но­го мас­си­ва ти­па int. Мас­сив мо­жет быть мно­го­мер­ным. При­чем в .NET мно­го­мер­ность мо­жет быть раз­ной.
    Вариант 1 - мно­го­мер­ный мас­сив ста­ро­го об­раз­ца, так на­зы­ва­емый мас­сив-мас­си­вов:
    
    int[][] int2dAr­ray;
    Система прос­тая - каж­дая скоб­ка соз­да­ет свой мас­сив. Т.е. мож­но пред­с­та­вить это в та­ком ви­де: (int[])[] ar­ray­Na­me - мас­сив ти­па int[], ко­то­рый то­же яв­ля­ет­ся мас­си­вом. По­доб­ных вло­же­ний мо­жет быть нес­коль­ко.
    Вариант 2 - мно­го­мер­ный мас­сив .NET:
    
    int[,] int2dAr­ray;
    
    Внутри скоб­ки ста­вит­ся нуж­ное ко­ли­чес­т­во за­пя­тых, каж­дая за­пя­тая соз­да­ет плюс од­но из­ме­ре­ние мас­си­ва. Т.е. в при­ве­ден­ной вы­ше строч­ке дек­ла­ри­ру­ет­ся таб­ли­ца (дву­мер­ный мас­сив) для це­ло­чис­лен­ных дан­ных.
    У каж­до­го ва­ри­ан­та есть свои плю­сы и свои ми­ну­сы. Ес­ли ко­рот­ко - вто­рой ва­ри­ант удоб­нее и чуть быс­т­рее ра­бо­та­ет, за­то пер­вый ва­ри­ант поз­во­ля­ет раз­де­лять мас­сив на сос­тав­ля­ющие при не­об­хо­ди­мос­ти. При­ме­ры ис­поль­зо­ва­ния мас­си­вов бу­дут по­том.
    
    Свойства
    Свойства - спе­ци­аль­ная фи­ча для воз­мож­нос­ти про­вер­ки дан­ных, вво­ди­мых в пе­ре­мен­ную и за­да­ния ка­ких-ли­бо дей­ст­вий при из­ме­не­нии зна­че­ния. Сво­его ро­да пос­ред­ник меж­ду пе­ре­мен­ной и про­чим ко­дом, а по сов­мес­ти­тель­с­т­ву - ох­ран­ник и сек­ре­тарь этой пе­ре­мен­ной :). Свой­ст­ва су­щес­т­ву­ют толь­ко внут­ри клас­са, вмес­те с пе­ре­мен­ны­ми уров­ня клас­са.
    Задаются очень прос­то:
    
    private flo­at _si­ze; //пе­ре­мен­ная
    public pro­perty flo­at Si­ze { //свой­ст­во - пос­ред­ник пе­ре­мен­ной _si­ze
    get {
    return _si­ze;
    }
    set {
    //проверить на пра­виль­ность
    _size = va­lue;
    OnSizeChanged();
    }
    }
    Сначала, как обыч­но, мо­ди­фи­ка­тор дос­ту­па. Клю­че­вое сло­во pro­perty ука­зы­ва­ет, что дек­ла­ри­ру­ет­ся свой­ст­во. Да­лее ука­зы­ва­ет­ся тип дан­ных, в при­ме­ре - оди­нар­ное дроб­ное. За­тем имя свой­ст­ва и от­к­ры­ва­ет­ся фи­гур­ная скоб­ка, на­чи­на­ющая блок опи­са­ния. Лю­бое свой­ст­во дол­ж­но иметь блок get - по­лу­чить. Ес­ли свой­ст­во толь­ко-для-чте­ния, оно не име­ет бло­ка set - ус­та­но­вить. В каж­дом бло­ке про­пи­сы­ва­ет­ся, что на­до сде­лать. Пом­ни­те, что свой­ст­во не хра­нит дан­ные, т.к. не яв­ля­ет­ся пе­ре­мен­ной - оно лишь пос­ред­ник, по­это­му в бло­ке get ука­за­но - вер­нуть зна­че­ние пе­ре­мен­ной _si­ze, в ко­то­рой зна­че­ние хра­нит­ся ре­аль­но. В бло­ке set на­пи­са­но, что де­лать при ус­та­нов­ке но­во­го зна­че­ния - сна­ча­ла про­ве­рить на пра­виль­ность... ну, нап­ри­мер, что­бы вво­ди­ли от 0 до 1, ког­да нуж­но и т.п. Пос­ле про­вер­ки - ус­та­но­вить зна­че­ние; клю­че­вое сло­во va­lue, в дан­ном слу­чае, ука­зы­ва­ет на вхо­дя­щее зна­че­ние. Ну и под за­на­вес - вы­пол­нить со­бы­тие "раз­мер из­ме­нил­ся". За­вер­ша­ет дек­ла­ра­цию зак­ры­ва­юща­яся фи­гур­ная скоб­ка.
    Свойства поз­во­ля­ют соз­да­вать все пе­ре­мен­ные как pri­va­te, а нуж­ные для от­к­ры­то­го дос­ту­па вы­во­дить че­рез свой­ст­ва, имея та­ким об­ра­зом га­ран­тию, что лю­бое внеш­нее из­ме­не­ние зна­че­ния бу­дет про­ве­ре­но на пра­виль­ность.
    
    Делегаты
    Делегаты - ес­ли прос­то, то это ука­за­те­ли на фун­к­ции. Ес­ли вам на­до пе­ре­дать фун­к­цию как ар­гу­мент, то у вас два пу­ти - ли­бо че­рез стро­ко­вое наз­ва­ние фун­к­ции ис­кать ее в биб­ли­оте­ке (assembly) и вы­зы­вать как внеш­нюю фун­к­цию, ли­бо ис­поль­зо­вать де­ле­гат. Де­ле­гат поз­во­ля­ет оп­ре­де­лить под­пись фун­к­ции - т.е. воз­в­ра­ща­емый тип, ти­пы и ко­ли­чес­т­во ар­гу­мен­тов.
    Делегат - это объ­ект уров­ня клас­са, соб­с­т­вен­но это и есть класс, толь­ко очень осо­бый. Од­на­ко дек­ла­ра­ция его очень по­хо­жа на дек­ла­ра­цию аб­с­т­рак­т­ной фун­к­ции:
    
    public de­le­ga­te do­ub­le MyDe­le­ga­te(do­ub­le d1, do­ub­le d2);
    Не за­бы­вай­те, что де­ле­гат - это класс, и для то­го, что­бы его мож­но бы­ло ис­поль­зо­вать, не­об­хо­ди­мо соз­дать объ­ект де­ле­га­та, ко­то­рый при­пи­сы­ва­ет­ся к кон­к­рет­ной фун­к­ции и даль­ше пе­ре­да­ет­ся, ес­ли ну­жен. У де­ле­га­та есть ме­тод In­vo­ke, ко­то­рый соб­с­т­вен­но и за­пус­ка­ет фун­к­цию, на ко­то­рую де­ле­гат ука­зы­ва­ет.
    Пример ис­поль­зо­ва­ния де­ле­га­та внут­ри фун­к­ции:
    
    MyDelegate md = new MyDe­le­ga­te(MyFun­c­ti­on);
    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 = сбор­ка, файл, яв­ля­ющий­ся кон­тей­не­ром для 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­sembly. Плю­сов в та­ком под­хо­де ма­ло­ва­то, по­это­му ред­ко ис­поль­зу­ет­ся. 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, а в VS2005 есть и ин­тер­фей­сные оп­ции в свой­ст­вах про­ек­та для этой це­ли.
    Если кто-то хо­чет ра­зоб­рать­ся под­роб­нее - есть неп­ло­хая статья (ку­сок кни­ги) на co­dep­ro­j­ect: лежит здесь - http://www.codeproject.com/books/1893115593_6.asp.
    
Глава 4. Основные операторы и конструкты.
    
    Рассмотрев син­так­сис дек­ла­ра­ций­, пе­ре­хо­дим к ос­нов­ным кон­с­т­рук­там или ко­ман­дам язы­ка. Как из­вес­т­но, код внут­ри фун­к­ции вы­пол­ня­ет­ся пос­т­роч­но, ес­ли не ис­поль­зу­ют­ся ка­кие-ли­бо ко­ман­ды. Дру­ги­ми сло­ва­ми - ко­ман­ды язы­ка приз­ва­ны обес­пе­чить не­ли­ней­ность вы­пол­не­ния ко­да. Са­мая из­вес­т­ная из не-ли­ней­ных ко­манд - go­to - есть в язы­ке C#, од­на­ко я нас­то­ятель­но не ре­ко­мен­дую ей поль­зо­вать­ся. Ос­таль­ных ко­манд впол­не дос­та­точ­но, что­бы обес­пе­чить лю­бую не­ли­ней­ность, а при­вы­кать к go­to - это как пра­ви­ло оз­на­ча­ет при­выч­ку к пло­хой струк­ту­ри­за­ции ко­да.
    Но сна­ча­ла, что­бы бы­ло про­ще по­ни­мать ко­ман­ды, рас­смот­рим не­ко­то­рые опе­ра­то­ры язы­ка.
    
    Операторы
    Операторы срав­не­ния:
    ‹ - стро­го мень­ше. Оп­ре­де­лен для лю­бых чис­ло­вых ти­пов (int, do­ub­le, short, byte, de­ci­mal, flo­at, bo­ol)
    › - стро­го боль­ше. Ана­ло­гич­но пре­ды­ду­ще­му.
    ‹= - мень­ше или рав­но. Ана­ло­гич­но пре­ды­ду­ще­му.
    ›= - боль­ше или рав­но. Ана­ло­гич­но пре­ды­ду­ще­му.)
    == - рав­но (экви­ва­лен­т­но). Оп­ре­де­лен для боль­шин­с­т­ва ти­пов и клас­сов. Для мно­гих клас­сов оз­на­ча­ет имен­но иден­тич­ность объ­ек­тов клас­са, а не ра­вен­с­т­во их внут­рен­них зна­че­ний.
    != - не рав­но. Ана­ло­гич­но пре­ды­ду­ще­му.
    Обратите вни­ма­ние: про­вер­ка на ра­вен­с­т­во обоз­на­ча­ет­ся дву­мя зна­ка­ми рав­но! Од­ним зна­ком обоз­на­ча­ет­ся опе­ра­тор прис­во­ения зна­че­ния.
    
    Логические опе­ра­то­ры:
    || - ло­ги­чес­кое ИЛИ.
    && - логическое И.
    ! - ло­ги­чес­кое НЕ.
    
    Арифметические опе­ра­то­ры:
    помимо стан­дар­т­ных +, -, *, / есть их мо­ди­фи­ка­ции со зна­ком рав­но: +=, -=, *=, /=. Оз­на­ча­ют "вы­пол­нить опе­ра­тор и при­рав­нять", т.е.
    
    i += 10;
    означает при­ба­вить 10 к зна­че­нию пе­ре­мен­ной i, и за­пи­сать ре­зуль­тат в нее же. Это ана­ло­гич­но за­пи­си:
    
    i = i + 10;
    есть еще два опе­ра­то­ра:
    ++ - при­ба­вить еди­ни­цу к пе­ре­мен­ной и за­пи­сать ре­зуль­тат в нее. Ин­к­ре­мен­т­ный опе­ра­тор.
    -- - от­нять еди­ни­цу от зна­че­ния пе­ре­мен­ной и за­пи­сать ре­зуль­тат в нее. Дек­ре­мен­т­ный опе­ра­тор.
    
    
    Основные кон­с­т­рук­ты мож­но раз­де­лить на цик­ло­вые - поз­во­ля­ющие за­пус­кать ку­сок ко­да в цик­ле, и ус­лов­ные - поз­во­ля­ющие вы­пол­нять нуж­ный ку­сок ко­да, вы­би­ра­емый по ус­ло­вию. Я опи­шу ос­нов­ные кон­с­т­рук­ты, ес­ли ка­кой за­бу­ду, а вам он ин­те­ре­сен - пи­ши­те.
    
    Циклы
    Самый рас­п­рос­т­ра­нен­ный цикл - for. Смыс­ло­вое пред­наз­на­че­ние - вы­пол­нять пос­ле­до­ва­тель­ность дей­ст­вий за­дан­ное ко­ли­чес­т­во раз. За­да­ет­ся сле­ду­ющим об­ра­зом:
    
    for (i = 0; i ‹ 10; i++) { /*Ваш код здесь*/ }
    После клю­че­во­го сло­ва for сле­ду­ет круг­лая скоб­ка, внут­ри кто­рой обя­за­тель­но при­сут­с­т­ву­ют три вы­ра­же­ния, раз­де­лен­ные точ­кой с за­пя­той. Пер­вое вы­ра­же­ние, в на­шем слу­чае "i=0" - опе­ра­ция, ко­то­рую на­до вы­пол­нить пе­ред на­ча­лом цик­ла. За­час­тую в этом мес­те пи­шут дек­ла­ра­цию пе­ре­мен­ной­, ко­то­рая су­щес­т­ву­ет толь­ко для цик­ла, тог­да вы­ра­же­ние выг­ля­дит, нап­ри­мер так: "int i = 0". С тем же ус­пе­хом по­ле мо­жет быть пус­тым. Вто­рое вы­ра­же­ние ус­та­нав­ли­ва­ет ус­ло­вие, при ко­то­ром цикл дол­жен про­дол­жать­ся... его мож­но рас­смат­ри­вать как "Про­дол­жать цикл по­ка вы­ра­же­ние 2 ис­тин­но". Мо­жет быть пус­тым, но тог­да вам са­мим при­дет­ся внут­ри цик­ла про­ве­рять ус­ло­вие вы­хо­да и са­мим же вы­хо­дить из цик­ла. Третье вы­ра­же­ние за­да­ет опе­ра­цию, ко­то­рую на­до вы­пол­нить по за­вер­ше­нии каж­до­го кру­га цик­ла. То­же мо­жет быть пус­тым.
    Итак в строч­ке при­ме­ра по­ка­зан цикл, об­ну­ля­ющий пе­ре­мен­ную i вна­ча­ле, ис­пол­ня­ющий­ся по­ка пе­ре­мен­ная i мень­ше 10, пос­ле каж­до­го кру­га уве­ли­чи­ва­ющий i на 1, что да­ет 10 вы­пол­не­ний цик­ла, при нор­маль­ных ус­ло­ви­ях.
    Для до­пол­ни­тель­но­го уп­рав­ле­ния вы­пол­не­ни­ем цик­ла пре­дус­мот­ре­ны еще два клю­че­вых сло­ва: con­ti­nue - за­вер­ша­ет те­ку­щий круг вы­пол­не­ния, bre­ak - за­вер­ша­ет цикл.
    
    for (int i = 0; i ‹ 10; i ++) { // цикл на 10 кру­гов
    if (so­me_array[i] == 0) { con­ti­nue;} // ес­ли i-тая ячей­ка мас­си­ва рав­на 0 - пой­ти на след круг
    int res = Do­So­met­hing(so­me_array[i]); // что-то сде­лать с ячей­кой мас­си­ва и вер­нуть зна­че­ние
    if (res == -1) { bre­ak;} // ес­ли вер­ну­лось -1 - прер­вать цикл
    }
    
    Цикл whi­le (быв­ший do...whi­le). Смыс­ло­вое пред­наз­на­че­ние - вы­пол­нять пос­ле­до­ва­тель­ность дей­ст­вий­, по­ка что-то не слу­чит­ся/изме­нит­ся.
    задается так:
    
    while (flag == true) { /*Ваш код здесь*/ }
    После клю­че­во­го сло­ва whi­le сле­ду­ет круг­лая скоб­ка, в ко­то­рой за­да­ет­ся ло­ги­чес­кое вы­ра­же­ние, про­ве­ря­емое на ис­тин­ность каж­дый круг цик­ла. Цикл вы­пол­ня­ет­ся по­ка вы­ра­же­ние ис­тин­но, или до клю­че­во­го сло­ва bre­ak, внут­ри цик­ла. Сло­во con­ti­nue при­ме­ни­мо к это­му цик­лу так же, как и к пре­ды­ду­ще­му.
    
    Цикл fo­re­ach. Смыс­ло­вое пред­наз­на­че­ние - вы­пол­нить ку­сок ко­да для каж­до­го чле­на мас­си­ва/спис­ка/кол­лек­ции. В .NET по­яви­лись клас­сы обес­пе­чи­ва­ющие не­ну­ме­ро­ван­ные, ди­на­ми­чес­кие мас­си­вы. В ос­нов­ном для них и был сде­лан этот опе­ра­тор.
    задается так:
    
    foreach (Class obj­ec­t­Na­me in col­lec­ti­on­Na­me) { /* Ваш код здесь */ }
    После клю­че­во­го сло­ва fo­re­ach сле­ду­ет круг­лая скоб­ка в ко­то­рой за­да­ет­ся класс оди­ноч­но­го объ­ек­та, над ко­то­рым вы­пол­ня­ет­ся опе­ра­ция в ос­нов­ном бло­ке, имя объ­ек­та (пе­ре­мен­ной­) для об­ра­ще­ния в ос­нов­ном бло­ке, за­тем клю­че­вое сло­во in и имя пе­ре­мен­ной­, ука­зы­ва­ющее на мас­сив/спи­сок/кол­лек­цию объ­ек­тов то­го клас­са, ко­то­рый ука­за­ли в на­ча­ле ус­ло­вия. Ос­нов­ной код, как обыч­но, пи­шет­ся в фи­гур­ных скоб­ках.
    Ну, нап­ри­мер, час­то встре­ча­ет­ся - цикл по всем фай­лам ка­ко­го-ли­бо ти­па в ка­та­ло­ге:
    
    DirectoryInfo di = new Di­rec­tor­yIn­fo("C:\\temp"); // соз­дать объ­ект ин­фор­ма­ции о ка­та­ло­ге С:\temp
    foreach (Fi­le­In­fo fi in di.Get­Fi­les("*.txt")) { // для каж­до­го объ­ек­та ти­па Fi­le­In­fo в кол­лек­ции, воз­в­ра­ща­емой фун­к­ци­ей Get­Fi­les
    // что-то сде­лать
    }
    
    Условия
    Условие if...else. Смыс­ло­вое пред­наз­на­че­ние - вы­пол­нить блок ко­да, толь­ко ес­ли ус­ло­вие ис­тин­но... с ва­ри­ан­том - ес­ли лож­но - вы­пол­нить дру­гой ку­сок ко­да.
    задается так:
    
    if (flag == true) { /* Ваш код здесь */ }
    else if (flag2 == true) { /* Ваш код здесь */ }
    else { /* Ваш код здесь */ }
    После клю­че­во­го сло­ва if сле­ду­ет ло­ги­чес­кое ус­ло­вие в круг­лых скоб­ках, при ис­тин­нос­ти ко­то­ро­го вы­пол­ня­ет­ся код, рас­по­ло­жен­ный в фи­гур­ных скоб­ках. Ес­ли ус­ло­вие лож­но - вы­пол­ня­ет­ся сле­ду­ющий блок. Сле­ду­ющий блок дол­жен на­чи­нать­ся с клю­че­во­го сло­ва el­se, за ко­то­рым мо­жет сле­до­вать сло­во if и еще од­но ус­ло­вие. Та­кая це­поч­ка мо­жет быть длин­ной­, но на­до пом­нить, что пос­ле вы­пол­не­ния хо­тя бы од­но­го бло­ка це­поч­ка пре­ры­ва­ет­ся.
    Пример:
    
    bool flag1 = true, flag2 = fal­se;
    if (flag1 && flag2) {Do­So­met­hing(); }
    else if (flag1) { Do­So­met­hing2(); }
    else { Do­So­met­hing3(); }
    В этом при­мер бу­дет вы­пол­не­на толь­ко фун­к­ция Do­So­met­hing2(). В пер­вом ус­ло­вии мы про­ве­ря­ем оба фла­га на ис­тин­ность, ес­ли хо­тя бы один не ра­вен ис­тин­но, пе­ре­хо­дим на вто­рой блок. Вто­рой блок то­же с ус­ло­ви­ем - про­ве­ря­ем пер­вый флаг на ис­тин­ность - мы точ­но зна­ем, что хо­тя бы один флаг ло­жен, ес­ли пер­вый ис­тин­нен - вто­рой ло­жен. Тре­тий блок - ес­ли пер­вый не ис­тин­нен, то ли­бо вто­рой ис­тин­нен, ли­бо оба лож­ны, но воз­мож­но нам это уже не важ­но...
    
    Пример 2:
    
    int i = 1;
    if (i == 0) { Do­So­met­hing1(); }
    else if (i == 1) { Do­So­met­hing2(); }
    else if (i == 2) {Do­So­met­hing3(); }
    else { Do­El­se(); }
    В этом, край­не нег­ра­мот­ном при­ме­ре, то­же бу­дет вы­пол­не­на толь­ко фун­к­ция Do­So­met­hing2(). А гра­мот­но та­кую си­ту­ацию рас­пи­сы­вать че­рез опе­ра­тор switch;
    
    Условие swit­ch...ca­se. Смыс­ло­вое пред­наз­на­че­ние - вы­пол­нять кус­ки ко­да по зна­че­нию пе­ре­мен­ной. Сво­е­об­раз­ная раз­вил­ка (или пе­рек­лю­ча­тель). С до­пол­ни­тель­ным воз­мож­нос­тя­ми.
    задется так:
    
    switch (var) {
    case ‹val1›:
    DoSomething();
    case ‹val2›:
    DoSomething2();
    break;
    default:
    DefaultAction();
    break;
    }
    После клю­че­во­го сло­ва switch сле­ду­ет имя пе­ре­мен­ной­, по чьему зна­че­нию нуж­но раз­вет­вить код. В фи­гур­ных скоб­ках за­да­ют­ся ва­ри­ан­ты зна­че­ний­, ко­то­рые дол­ж­ны об­ра­ба­ты­вать­ся. Ва­ри­ант зна­че­ния за­да­ет­ся как клю­че­вое сло­во ca­se, зна­че­ние пе­ре­мен­ной­, дво­ето­чие. Ес­ли фун­к­ци­ональ­ный блок за­кан­чи­ва­ет­ся сло­вом bre­ak - прог­рам­ма вы­хо­дит из бло­ка switch пос­ле не­го, ес­ли не за­кан­чи­ва­ет­ся - прог­рам­ма пе­ре­хо­дит на вы­пол­не­ние сле­ду­юще­го по по­ряд­ку бло­ка ca­se. клю­че­вое сло­во de­fa­ult на мес­те ca­se оз­на­ча­ет "все ва­ри­ан­ты".
    Пример:
    
    int i = 1;
    switch (i) {
    case 2:
    DoSomething2();
    break;
    case 1:
    DoSomething1();
    case 3:
    DoSomething3();
    break;
    default:
    DoDefault();
    break;
    }
    В этом при­ме­ре бу­дут вы­пол­не­ны фун­к­ции Do­So­met­hing1() и Do­So­met­hing3().
    Переменная мо­жет быть не чис­ло­вой­:
    
    char c = '!';
    switch (c) {
    case '?':
    DoQuestion();
    break;
    case '!':
    DoExclamation();
    break;
    }
    В этом при­ме­ре бу­дет вы­пол­не­на толь­ко фун­к­ция Do­Ex­c­la­ma­ti­on(), а ес­ли пе­ре­мен­ная с бу­дет рав­на не воп­ро­си­тель­но­му зна­ку и не вос­к­ли­ца­тель­но­му - ни­че­го сде­ла­но не бу­дет.
    
    По по­во­ду на­хож­де­ния ос­нов­но­го ко­да внут­ри фи­гур­ных ско­бок - ес­ли код сос­то­ит из од­ной строч­ки, мож­но пи­сать его без фи­гур­ных ско­бок:
    
    if (flag == true) re­turn;
    
Гла­ва 5. Ини­ци­али­за­ция пе­ре­мен­ных. Кол­лек­ции. Ти­пы дан­ных.
    
    
    Инициализация.
    Я уже упо­ми­нал это сло­во, в та­ком при­мер­но кон­тек­с­те - Пос­ле дек­ла­ра­ции пе­ре­мен­ной­, пе­ред ее ис­поль­зо­ва­ни­ем, не­об­хо­ди­мо эту пе­ре­мен­ную ин­ци­али­зи­ро­вать. Поп­ро­бу­ем ра­зоб­рать­ся точ­нее: ини­ци­али­за­ция - про­цесс вы­де­ле­ния па­мя­ти под пе­ре­мен­ную и за­пол­не­ние этой па­мя­ти зна­че­ни­ями по умол­ча­нию. Зна­че­ния по умол­ча­нию - это, поч­ти всег­да, раз­ные фор­мы ну­ля: для int - 0, для do­ub­le - 0.0, для string - "", для bo­ol - fal­se и т. д.
    Та часть, ко­то­рая ка­са­ет­ся вы­де­ле­ния па­мя­ти, это те­перь де­ло ком­пи­ля­то­ра и к прог­рам­мис­ту поч­ти не име­ет ни­ка­ко­го от­но­ше­ния. С точ­ки зре­ния прог­рам­мис­та, ини­ци­али­за­ция - пер­вое прис­во­ение зна­че­ния пе­ре­мен­ной.
    Есть та­кая тон­кость - пе­ре­мен­ные, объ­яв­лен­ные на уров­не клас­са ини­ци­али­зи­ру­ют­ся ав­то­ма­ти­чес­ки при соз­да­нии объ­ек­та клас­са. Но это от­но­сит­ся толь­ко к ос­нов­ным ти­пам дан­ных (int, do­ub­le, bo­ol, byte и т. д.) кро­ме string. Все пе­ре­мен­ные клас­сов по­лу­ча­ют при ав­то­ма­ти­чес­кой ини­ци­али­за­ции зна­че­ние null и string то­же.
    Комментарий для спе­ци­алис­тов: ес­ли быть точ­ным, то null - это зна­че­ние по умол­ча­нию для всех пе­ре­мен­ных клас­сов Mar­s­hal­B­y­Re­fe­ren­ce - об­ра­ба­ты­ва­емых че­рез ука­за­тель. Ос­нов­ные ти­пы дан­ных яв­ля­ют­ся клас­са­ми Mar­s­hal­B­y­Va­lue - об­ра­ба­ты­ва­емых че­рез зна­че­ние, по­это­му им прис­ва­ива­ют­ся нор­маль­ные зна­че­ния. Класс string по преж­не­му яв­ля­ет­ся мас­си­вом, хоть и очень хит­рым, и об­ра­ба­ты­ва­ет­ся со­от­вет­с­т­вен­но.
    Инициализация бы­ва­ет двух ти­пов:
    для прос­тых ти­пов дан­ных это прос­тое прис­во­ение зна­че­ния:
    
    int i = 0;
    double d = 0.2;
    string s = "test string"
    Все это ва­ри­ан­ты дек­ла­ра­ции с од­нов­ре­мен­ной ини­ци­али­за­ци­ей для прос­тых ти­пов дан­ных.
    Второй ва­ри­ант - ини­ци­али­за­ция объ­ек­та клас­са:
    
    DateTime dt = new Da­te­Ti­me();
    MyClass mc = new MyClass();
    int[] in­tAr­ray = new int[20];
    Обратите вни­ма­ние - мас­си­вы рас­смат­ри­ва­ют­ся как клас­сы.
    Для спе­ци­алис­тов: мас­сив, как из­вес­т­но, об­ра­ба­ты­ва­ет­ся по ука­за­те­лю. А прос­тые ти­пы дан­ных то­же мо­гут быть ини­ци­али­зи­ро­ва­ны как клас­сы: int i = new int(); впол­не пра­виль­ная за­пись. Бо­лее то­го, за­пись "int i = 2;" для ком­пи­ля­то­ра рав­на "int i = new int(); i = 2;".
    Инициализировать пе­ре­мен­ную мож­но поч­ти в лю­бой мо­мент - глав­ное, до пер­во­го об­ра­ще­ния. Та­кая за­пись впол­не до­пус­ти­ма:
    
    public par­ti­al class Form1 : Form
    {
    public Da­te­Ti­me dt = 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:
    
    ArrayList al = new Ar­ray­List();
    int i = 2;
    double d = 4.5;
    string s = "test";
    MyClass mc = new MyClass();
    al.Add(i);
    al.Add(d);
    al.Add(s);
    al.Add(mc);
    Таким пу­тем мы до­ба­ви­ли са­мые раз­ные эле­мен­ты в спи­сок и мо­жем до лю­бо­го из них дос­ту­чать­ся как до эле­мен­та мас­си­ва:
    
    string s2 = (string)al[2];
    s2 бу­дет рав­но "test". Есть толь­ко од­но не­удоб­с­т­во - Ar­ray­List всег­да воз­в­ра­ща­ет по­ме­щен­ный в не­го объ­ект как obj­ect, по­это­му для нор­маль­ной ра­бо­ты нуж­но пре­об­ра­зо­вы­вать воз­в­ра­ща­емое зна­че­ние в нуж­ный тип... что не всег­да прос­то. Имен­но это пре­об­ра­зо­ва­ние и со­вер­ша­ет сло­во string, сто­ящее в круг­лых скоб­ках пе­ред об­ра­ще­ни­ем al[2]. Под­роб­нее об этом - чуть ни­же.
    
    Типы дан­ных
    У каж­дой пе­ре­мен­ной есть свой тип дан­ных или прос­то тип, то же мож­но ска­зать о воз­в­ра­ща­емом лю­бой фун­к­ци­ей зна­че­нии (vo­id то­же тип, хо­тя и осо­бый­). В .NET все ти­пы дан­ных яв­ля­ют­ся до­чер­ни­ми от Obj­ect, у ко­то­ро­го есть все­го 4 фун­к­ции, за­то 2 из них нуж­ны очень час­то:
    Object.ToString() - воз­в­ра­ща­ет стро­ко­вое пред­с­тав­ле­ние об объ­ек­те (для чи­сел - стро­ку с чис­лом, для слож­ных объ­ек­тов - не­кую стро­ку, опи­сы­ва­ющую глав­ную ин­фор­ма­цию объ­ек­та; нап­ри­мер Fi­le­In­fo - класс ин­фор­ма­ции о фай­ле - в ме­то­де ToS­t­ring() воз­в­ра­ща­ет пол­ный путь фай­ла.)
    Object.GetType() - воз­в­ра­ща­ет объ­ект клас­са Type, опи­сы­ва­ющий тип дан­ных, ко­то­ро­му при­над­ле­жит ис­сле­ду­емый объ­ект.
    Для срав­не­ния ти­пов есть спе­ци­аль­ный опе­ра­тор 'is'. Ис­поль­зу­ет­ся так:
    
    if (var is int) { do so­met­hing }
    вместо var - имя пе­ре­мен­ной­, вмес­то int - тип дан­ных (имя клас­са) с ко­то­рым вы хо­ти­те срав­нить.
    Рассмотрим при­мер из пре­ды­ду­ще­го па­раг­ра­фа - у нас есть Ar­ray­List из 4 объ­ек­тов, 4 раз­ных ти­пов:
    
    for (int i = 0; i ‹ al.Length; i ++) {
    if (al[i] is int) { /*сде­лать что-то с це­лым чис­лом */ (int)al[i]}
    else if (al[i] is do­ub­le} { /*сде­лать что-то с дроб­ным чис­лом*/ (do­ub­le)al[i]}
    else if (al[i] is string} { /*сде­лать что-то со стро­кой­*/ (string)al[i]}
    else { Mes­sa­ge­Box.Show(al[i].ToS­t­ring()); } // по­ка­зать со­об­ще­ние со стро­ко­вым пред­с­тав­ле­ни­ем объ­ек­та
    }
    В этом при­ме­ре мы про­бе­га­ем цик­лом по спис­ку, смот­рим на тип оче­ред­ной пе­ре­мен­ной­, ес­ли это что-то прос­тое - пре­об­ра­зу­ем и что-то де­ла­ем, ес­ли нет - по­ка­зы­ва­ем в со­об­ще­нии стро­ко­вое пред­с­тав­ле­ние объ­ек­та.
    Теперь о пре­об­ра­зо­ва­нии. Пре­об­ра­зо­ва­ни­ем ти­пов дан­ных на­зы­ва­ют два раз­ных про­цес­са.
    Один - это кон­вер­та­ция (Con­vert) - нас­то­ящая сме­на ти­па. Нап­ри­мер, прев­ра­тить do­ub­le в int - т.е. от­б­ро­сить дроб­ную часть, или ок­руг­лить до бли­жай­ше­го це­ло­го. Или, еще нап­ри­мер, прев­ра­тить do­ub­le в string - т.е. соз­дать стро­ку, где за­пи­са­но чис­ло, хра­ни­мое в пе­ре­мен­ной.
    Второй - это сме­на ти­па (cast) - без за­ме­ны дан­ных. Пе­ре­мен­ная ти­па obj­ect мо­жет на са­мом де­ле хра­нить лю­бой объ­ект, и что­бы ком­пи­ля­тор по­нял, что от не­го хо­тят - на­до пре­об­ра­зо­вы­вать. Бы­ва­ют и бо­лее слож­ные ве­щи.
    Конвертация со­вер­ша­ет­ся всег­да че­рез фун­к­цию, ко­то­рая зна­ет, как имен­но на­до пре­об­ра­зо­вы­вать ти­пы. А все, что ка­са­ет­ся строк - на­до знать еще и нас­т­рой­ки куль­ту­ры для ко­то­рой ве­дет­ся пре­об­ра­зо­ва­ние. Есть спе­ци­аль­ный класс Con­vert, ко­то­рый ве­да­ет кон­вер­та­ци­ей. Им и ре­ко­мен­ду­ет­ся поль­зо­вать­ся.
    
    double d = 4.2;
    string s1 = d.ToS­t­ring();
    string s2 = Con­vert.ToS­t­ring(4.2);
    s1 и s2 - оди­на­ко­вы.
    Преобразование мо­жет быть: встро­ен­ным (impli­cit - не­яв­ным), над­с­т­ро­ен­ным (expli­cit - яв­ным) и еще од­ним :)... на­зо­вем его ус­лов­ным.
    Встроенное - оно ли­бо есть, ли­бо нет. Обес­пе­чи­ва­ет­ся соз­да­те­лем клас­са. Т.е. нап­ри­мер int в do­ub­le пре­об­ра­зу­ет­ся лег­ко, са­мим ком­пи­ля­то­ром, а вот на­обо­рот - толь­ко че­ло­ве­ком.
    Надстроенное - это то, что пи­шет­ся в скоб­ках пе­ред име­нем пе­ре­мен­ной. Нап­ри­мер, мож­но вот так вот сде­лать:
    
    CheckBox cb = new Chec­k­Box();
    object obj = cb;
    ((CheckBox)obj).Checked = true;
    Условное ра­бо­та­ет толь­ко с объ­ек­та­ми клас­сов и не ра­бо­та­ет с прос­ты­ми ти­па­ми дан­ных, кро­ме string. Выг­ля­дит оно как опе­ра­тор 'as' - 'как' (рас­смат­ри­вать как).
    
    string s = al[2] as string;
    Оператор го­во­рит ком­пи­ля­то­ру, что под­со­вы­ва­емую ему пе­ре­мен­ную на­до рас­смат­ри­вать "как" ука­зан­ный тип дан­ных.
    
Гла­ва 6. При­мер ко­да.
    
    Итак, для пер­вой час­ти, я ду­маю дос­та­точ­но. С тем, что уже бы­ло рас­смот­ре­но, мож­но на­чи­нать прог­рам­ми­ро­вать, а зна­ющие дру­гой язык дол­ж­ны бы­ли уже по­лу­чить пред­с­тав­ле­ние о C#. Ос­та­лись не­рас­смот­рен­ны­ми еще мно­гие ве­щи, вклю­чая не­ко­то­рые из ос­нов­ных, но в си­лу то­го, что они нес­коль­ко слож­нее - я рас­смот­рю их в сле­ду­ющих гла­вах.
    
    Пример ко­да:
    
    namespace tst_form2 //за­да­ем na­mes­pa­ce
    {
    public de­le­ga­te do­ub­le De­le­ga­te1(); // в нем дек­ла­ри­ру­ем де­ле­га­та
    public ab­s­t­ract class MyPa­ren­t­C­lass // соз­да­ем класс, аб­с­т­рак­т­ный
    : Obj­ect, tst_form3.IIn­ter­fa­ce1 // по­рож­ден­ный от клас­са Obj­ect с под­к­лю­чен­ным ин­тер­фей­сом tst_form3.IIn­ter­fa­ce1
    {
    private int int1; // внут­рен­нее по­ле, дос­туп­ное толь­ко внут­ри клас­са
    protected int Int1 { // свой­ст­во для это­го по­ля, дос­туп­ное нас­лед­ни­кам
    get { re­turn int1; } // воз­в­рат­ный код
    set { //уста­но­воч­ный код
    if (va­lue › 0) { // про­вер­ка на зна­че­ние
    int1 = va­lue; // ес­ли прош­ли - ус­та­но­вить зна­че­ние
    }
    else throw new Ar­gu­men­tEx­cep­ti­on("Va­lue must be › 0", "Int1"); // ес­ли нет - ки­нуть ошиб­ку
    }
    }
    public do­ub­le d1; // от­к­ры­тые по­ля
    public do­ub­le d2; //
    public MyPa­ren­t­C­lass() { //основ­ной кон­с­т­рук­тор клас­са
    int1 = 0; // ини­ци­али­зи­ру­ем пе­ре­мен­ную на­чаль­ным зна­че­ни­ем
    }
    public MyPa­ren­t­C­lass(do­ub­le inD1, do­ub­le inD2) // до­пол­ни­тель­ный кон­с­т­рук­тор клас­са
    : this() { // ко­то­рый сна­ча­ла за­пус­ка­ет ос­нов­ной кон­с­т­рук­тор и толь­ко по­том ис­пол­ня­ет­ся
    d1 = inD1; // ини­ци­али­зи­ру­ем пе­ре­мен­ные вхо­дя­щи­ми ар­гу­мен­та­ми
    d2 = inD2; // ес­ли ар­гу­мен­тов нет - пе­ре­мен­ные ини­ци­али­зи­ру­ют­ся са­ми, стан­дар­т­ны­ми зна­че­ни­ями
    }
    protected ab­s­t­ract do­ub­le Get­Sum(); // шаб­лон фун­к­ции для ини­ци­али­за­ции в до­чер­них клас­сах
    #region IIn­ter­fa­ce1 Mem­bers // фун­к­ции ин­тер­фей­са
    public ab­s­t­ract do­ub­le Get­Sub­s­t­ract(); // фун­к­ция ин­тер­фей­са - то­же толь­ко шаб­лон
    #endregion
    }
    namespace tst_form3 // объ­яв­ля­ем еще na­mes­pa­ce внут­ри tst_form2
    {
    public class MyChil­d­C­lass //соз­да­ем класс
    : tst_form2.MyPa­ren­t­C­lass //по­рож­ден­ный от tst_form2.MyPa­ren­t­C­lass
    {
    private De­le­ga­te1 get­Sum­Del; // дек­ла­ри­ру­ем объ­ект де­ле­га­та
    internal MyChil­d­C­lass() //основ­ной кон­с­т­рук­тор
    : ba­se() { // за­пус­ка­ющий ро­ди­тель­с­кий и толь­ко по­том ис­пол­ня­ющий­ся
    getSumDel = new De­le­ga­te1(Get­Sum); // ини­ци­али­зи­ру­ем объ­ект де­ле­га­та - на фун­к­цию Get­Sum
    }
    internal MyChil­d­C­lass(do­ub­le inD1, do­ub­le inD2) //до­пол­ни­тель­ный кон­с­т­рук­тор
    : ba­se(inD1, inD2) { // за­пус­ка­ющий до­пол­ни­тель­ный ро­ди­тель­с­кий кон­с­т­рук­тор
    getSumDel = new De­le­ga­te1(Get­Sum); // ини­ци­али­зи­ру­ющий объ­ект де­ле­га­та
    }
    #region in­he­ri­ted mem­bers // унас­ле­до­ван­ные фун­к­ции
    protected over­ri­de do­ub­le Get­Sum() { // оп­ре­де­ля­ем ро­ди­тель­с­кую аб­с­т­рак­т­ную фун­к­цию
    return d1 + d2; //воз­в­ра­ща­ем сум­му
    }
    public over­ri­de do­ub­le Get­Sub­s­t­ract() { // оп­ре­де­ля­ем ро­ди­тель­с­кую аб­с­т­рук­т­ную фун­к­цию
    return d1 - d2; // воз­в­ра­ща­ем раз­ность
    }
    #endregion
    internal vo­id Chan­ge­Int1(int inInt) { // соз­да­ем фун­к­цию для сме­ны зна­че­ния
    Int1 = inInt;
    }
    public do­ub­le TryTo­Get­Sum() { //фун­к­ция по­лу­че­ния сум­мы
    return Sta­tic­Func.Get­Sum­F­rom­C­hil­d­C­lass(get­Sum­Del); // вы­зы­ва­ем ста­тич­ную фун­к­цию дру­го­го клас­са с де­ле­га­том для Get­Sum
    }
    }
    public in­ter­fa­ce IIn­ter­fa­ce1 //интер­фей­с
    {
    double Get­Sub­s­t­ract(); // фун­к­ции ин­тер­фей­сов объ­яв­ля­ют­ся без мо­ди­фи­ка­то­ров
    }
    }
    internal class Sta­tic­Func //класс для на­шей ста­тич­ной фун­к­ции
    {
    public sta­tic do­ub­le Get­Sum­F­rom­C­hil­d­C­lass(De­le­ga­te1 del) {
    return del.Invo­ke(); // воз­в­ра­ща­ем ре­зуль­тат вы­зо­ва фун­к­ции, при­пи­сан­ной к де­ле­га­ту
    }
    public enum Sup­por­ted­Lan­gu­ages //прос­той Enum
    {
    Русский = 1,
    English = 10,
    German = 11
    }
    public sta­tic obj­ect[] obj­Ar­ray = {1, 2.4, "string", Sup­por­ted­Lan­gu­ages.Рус­ский }; // ста­тич­ный мас­сив
    }
    }//закрываем na­mes­pa­ce tst_form2
    Для тес­т­ро­ва­ния - на­пи­шем еще та­кую фун­к­цию в лю­бом дру­гом na­mes­pa­ce... хоть в ос­нов­ном, соз­дан­ном ав­то­ма­ти­чес­ки при соз­да­нии про­ек­та:
    
    private vo­id but­ton1_Click(obj­ect sen­der, Even­tArgs e) { //при на­жа­тии на кноп­ку
    tst_form2.tst_form3.MyChildClass mcc = new tst_form2.tst_form3.MyChil­d­C­lass(1.2, 2.4); // соз­да­ем объ­ект на­ше­го клас­са с вве­ден­ны­ми зна­че­ни­ями
    double tmp_do­ub­le = mcc.TryTo­Get­Sum(); //вер­нет 3.6
    tmp_double = mcc.Get­Sub­s­t­ract(); //вер­нет -1.2
    /*mcc.ChangeInt1(-2); //про­изой­дет ошиб­ка - не­вер­ный па­ра­метр*/
    mcc.ChangeInt1(2); //int1 ста­нет ра­вен 2
    int i1 = (int)tst_form2.Sta­tic­Func.Sup­por­ted­Lan­gu­ages.English; // i1 = 10
    string s = tst_form2.Sta­tic­Func.Sup­por­ted­Lan­gu­ages.English.ToS­t­ring();//s="English"
    s = "";
    for (int i = 0; i ‹ tst_form2.Sta­tic­Func.obj­Ar­ray.Length; i++) {
    s += tst_form2.Sta­tic­Func.obj­Ar­ray[i].ToS­t­ring() + "\t";
    if (tst_form2.Sta­tic­Func.obj­Ar­ray[i] is int) s += "\n";
    else {
    switch (tst_form2.Sta­tic­Func.obj­Ar­ray[i].Get­T­y­pe().ToS­t­ring()) {
    case "System.Do­ub­le":
    s += Math.Ro­und((do­ub­le)tst_form2.Sta­tic­Func.obj­Ar­ray[i]).ToS­t­ring() + "\n";
    break;
    case "tst_form2.Sta­tic­Func+Sup­por­ted­Lan­gu­ages":
    s += ((int)(tst_form2.Sta­tic­Func.Sup­por­ted­Lan­gu­ages)tst_form2.Sta­tic­Func.obj­Ar­ray[i]).ToS­t­ring();
    break;
    default:
    s += "\n";
    break;
    }
    }
    } // цикл за­вер­ша­ет­ся s = "1
    //2.4 2
    //string
    //Русский 1"
    }
    
    Немного до­пол­не­ний и ком­мен­та­ри­ев:
    Если у фун­к­ции есть мо­ди­фи­ка­тор over­ri­de - ее мож­но пе­ре­оп­ре­де­лять во всех до­чер­них клас­сах, лю­бо­го по­ко­ле­ния.
    Если вы хо­ти­те сра­зу оп­ре­де­лить фун­к­цию, но дать ей воз­мож­ность быть пе­ре­оп­ре­де­лен­ной в нас­лед­ни­ках - прис­вой­те мо­ди­фи­ка­тор vir­tu­al.
    Последовательное вы­пол­не­ние ус­ло­вий в switch бло­ке воз­мож­но толь­ко ес­ли ca­se ус­ло­вия оп­ре­де­ле­ны чис­ла­ми.
    По по­во­ду плю­си­ка в име­ни ти­па Enum - Enum, по ло­ги­ке, на­до объ­яв­лять в na­mes­pa­ce, на­рав­не с клас­са­ми. Од­на­ко, мож­но их за­пи­хать и внутрь клас­са. Ес­ли их объ­явить в na­mes­pa­ce - имя ти­па бу­дет пос­т­ро­ено как обыч­но, а ес­ли внут­ри клас­са - то че­рез плю­сик.
    
Часть 2.
    Чуть бо­лее слож­ные ве­щи в язы­ке C# и плат­фор­ме .NET.
Глава 1. Модификаторы аргументов. Регулярные выражения.
    
    Рассмотрим мо­ди­фи­ка­то­ры ар­гу­мен­тов фун­к­ций.
    ref - пе­ре­да­ет в ка­чес­т­ве ар­гу­мен­та ссыл­ку на объ­ект. Та­ким об­ра­зом вы по­лу­ча­ете воз­мож­ность из­ме­нять объ­ект, не соз­да­вая но­вый­, и не воз­в­ра­щая его, как ре­зуль­тат фун­к­ции.
    Пример:
    
    private vo­id Func1(ref string str) {
    str = "но­вая стро­ка";
    }
    private vo­id Ma­in­Func() {
    string str = "стро­ка";
    Func1(ref str); // str = "но­вая стро­ка"
    }
    out - пе­ре­да­ет в ка­чес­т­ве ар­гу­мен­та ссыл­ку на не­ини­ци­али­зи­ро­ван­ный объ­ект. Та­ким об­ра­зом вы по­лу­ча­ете воз­мож­ность воз­в­ра­щать мно­жес­т­вен­ные ре­зуль­та­ты ра­бо­ты фун­к­ции.
    Пример:
    
    private vo­id Func1(out string re­sult1, out int re­sult2, out obj­ect re­sult3) {
    result1 = 10;
    result2 = "ре­зуль­тат";
    result3 = new obj­ect();
    }
    private vo­id Ma­in­Func() {
    int r1;
    string r2;
    object r3;
    Func1(out r1, out r2, out r3);
    }
    Разница меж­ду ref и out толь­ко в том, что out не тре­бу­ет ини­ци­али­за­ции ар­гу­мен­та до вы­зо­ва фун­к­ции, а тре­бу­ет ее внут­ри фун­к­ции. ref - на­обо­рот, тре­бу­ет пе­ре­да­чи ини­ци­али­зи­ро­ван­но­го объ­ек­та.
    
    param - очень хит­рая вещь, поз­во­ля­ет де­лать фун­к­ции с пе­ре­мен­ным ко­ли­чес­т­вом ар­гу­мен­тов. Тре­бо­ва­ния прос­ты - pa­ram ар­гу­мент дол­жен быть пос­лед­ним в спис­ке ар­гу­мен­тов, ар­гу­мен­ты дол­ж­ны быть од­но­го ти­па, ар­гу­мен­ты за­да­ют­ся как мас­сив.
    Например так:
    
    private vo­id Func1(byte b1, pa­ram int[] da­ta) {}
    Если вам не­об­хо­ди­мо, что­бы ар­гу­мен­ты бы­ли раз­но­тип­ны - счи­тай­те их ти­пом obj­ect. Еще один мо­мент - вмес­то спис­ка ар­гу­мен­тов в стро­ке, мож­но пе­ре­да­вать мас­сив... но тут на­до быть ос­то­рож­ным:
    
    private vo­id Func1(byte b1, pa­ram obj­ect[] da­ta) { }
    private vo­id Ma­in­Func() {
    Func1(1, 1, "str", new obj­ect()); // все нор­маль­но. da­ta - мас­сив ти­па obj­ect из трех чле­нов.
    Func1(2, new obj­ect[] {1, "str", new obj­ect()}); // все нор­маль­но, da­ta - как в пре­ды­ду­щем ва­ри­ан­те.
    Func1(3, 1, new obj­ect[]{"str", new obj­ect()}); // da­ta - мас­сив ти­па obj­ect из двух чле­нов, вто­рой - мас­сив ти­па obj­ect из 2х чле­нов.
    }
    
 Регулярные вы­ра­же­ния
    В об­щем ви­де ре­гу­ляр­ные вы­ра­же­ния ( = re­gu­lar ex­p­res­si­ons = 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­res­so и пр.
    Возьмем для при­ме­ра прос­той шаб­лон:
    
    (?‹Protocol›\w+):\/\/(?‹Domain›[\w\.]+)\/?\S*
    Этот шаб­лон вы­дер­ги­ва­ет из пред­ло­жен­но­го тек­с­та все URL, и сох­ра­ня­ет их с вы­де­ле­ни­ем про­то­ко­ла и до­ме­на.
    Код для ис­поль­зо­ва­ния:
    
    void Re­gex­Match() {
    string inStr = "blah-blah-blah so­met­hing he­re http://www.do­ma­in.com/index.cgi and so­me com­ments he­re and fi­le he­re ftp://ftp.do­ma­in123.info/re­po­si­tory/";
    StringBuilder sb = new Strin­g­Bu­il­der();
    Regex r = new Re­gex(@"(?‹Pro­to­col›\w+):\/\/(?‹Do­ma­in›[\w\.]+)\/?\S*", Re­ge­xOp­ti­ons.Igno­re­Ca­se | RegexOptions.Singleline);
    for (Match m = r.Mat­ch(inStr); m.Suc­cess; m = m.Nex­t­Match()) {
    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);
    }
    textBox1.Text = sb.ToS­t­ring();
    }
    Результат бу­дет:
    
    Найдено URL: http://www.do­ma­in.com/index.cgi, про­то­кол: http, до­мен: www.do­ma­in.com
    Найдено URL: ftp://ftp.do­ma­in123.info/re­po­si­tory/, про­то­кол: ftp, до­мен: ftp.do­ma­in123.info
    
    Примеров мо­жет быть мно­жес­т­во, луч­ше пос­мот­ри­те шаб­ло­ны в дей­ст­вии... а в Ex­p­res­so есть сис­те­ма ин­те­рак­тив­но­го сос­тав­ле­ния шаб­ло­на... очень по­лез­но для обу­че­ния, да и при­ме­ров у них мно­го.
    
Гла­ва 2. Ге­не­ало­гия клас­сов.
    
    Итак, по­го­во­рим ког­да ко­го и как нуж­но по­рож­дать.
    Один из пос­ту­ла­тов сов­ре­мен­но­го вы­со­ко­уров­не­во­го прог­рам­ми­ро­ва­ния, не всег­да вер­ных, впро­чем, гла­сит, что "Чем мень­ше оди­на­ко­вых бло­ков ко­да в прог­рам­ме - тем луч­ше". Пер­вое след­с­т­вие из это­го пос­ту­ла­та - блок ко­да, ко­то­рый ис­поль­зу­ет­ся боль­ше, чем в од­ном мес­те прог­рам­мы дол­жен быть вы­не­сен в от­дель­ную фун­к­цию. Впро­чем, это к те­ме не от­но­сит­ся. Вто­рое след­с­т­вие - на­бор пе­ре­мен­ных/фун­к­ций­, ко­то­рые ис­поль­зу­ют­ся боль­ше чем в од­ном клас­се дол­ж­ны быть вы­де­ле­ны в от­дель­ный ро­ди­тель­с­кий класс. В об­щем-то, этим пра­ви­лом мож­но ру­ко­вод­с­т­во­вать­ся при оп­ре­де­ле­нии "ну­жен ли вам не­кий ро­ди­тель для ва­ших клас­сов?", ес­ли у вас их мно­го.
    
    Рассмотрим мо­дель­ную си­ту­ацию с мно­жес­т­вом клас­сов, и оп­ре­де­лим ка­кие им нуж­ны род­с­т­вен­ные свя­зи.
    
    Довольно клас­си­чес­кий при­мер - эле­мен­ты уп­рав­ле­ния. Рас­смот­рим 4 эле­мен­та уп­рав­ле­ния: кноп­ка, тек­с­то­вое по­ле, па­нель и груп­па. У всех этих эле­мен­тов дол­ж­ны быть свой­ст­ва: по­ло­же­ние, раз­мер, со­бы­тия на­жа­тия, фо­ку­са и еще мож­но мно­го вся­ких на­пи­сать для удоб­с­т­ва прог­рам­мис­тов. Ста­ло быть один пре­док, об­щий для всех эле­мен­тов уп­рав­ле­ния на­шел­ся. Ду­ма­ем даль­ше - па­нель и груп­па дол­ж­ны иметь под­чи­нен­ные эле­мен­ты уп­рав­ле­ния, а так­же уметь прок­ру­чи­вать соб­с­т­вен­ную внут­рен­нюю об­ласть. Ста­ло быть у этих дво­их дол­жен быть еще об­щий пре­док. Итак струк­ту­ра по­лу­чить­ся при­мер­но та­кой­:
    
    Ну вот вам об­щий прин­цип вы­де­ле­ния об­щих пред­ков.
    
    Рассмотрим воп­ро­сы нас­лед­с­т­ва, ог­ра­ни­че­ния на нас­лед­с­т­во, рас­т­ран­жи­ри­ва­ния нас­лед­с­т­ва и сте­ри­ли­за­ции.
    
    Многое из то­го, о чем сей­час пой­дет речь уже рас­ска­зы­ва­лось в час­ти 1 гла­ве 1 в раз­де­ле о дек­ла­ра­ци­ях.
    Главное пра­ви­ло нас­ле­до­ва­ния - каж­дый по­то­мок нас­ле­ду­ет все. Т.е. все чле­ны клас­са, от­к­ры­тые для нас­ле­до­ва­ния, пе­ре­да­ют­ся каж­до­му по­том­ку в лю­бом ко­ле­не. Един­с­т­вен­ное, что мо­жет по­ме­шать - пе­ре­оп­ре­де­ле­ние чле­на клас­са.
    
 По по­во­ду "как жить по­том­кам":
    Итак, мо­ди­фи­ка­тор 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, по­том­ке клас­са А с ис­поль­зо­ва­ние сло­ва new:
    
    B b1 = new B();
    b1.Func1(); // но­вый ва­ри­ант фун­к­ции.
    ((A)b1).Func1(); // ста­рый ва­ри­ант фун­к­ции
 И са­мое страш­ное - сте­ри­ли­за­ция.
    Очень прос­то де­ла­ет­ся, как и все ужа­сы в на­шем ми­ре. Мо­ди­фи­ка­тор клас­са se­aled - сте­ри­ли­зу­ет его, и у не­го уже ни­ког­да не бу­дет де­тей :(. Тот же мо­ди­фи­ка­тор, при­ме­нен­ный к vir­tu­al фун­к­ции от­ме­ня­ет ее вир­ту­аль­ность.
    
 И на­пос­ле­док - крес­т­ные (отцы/ма­те­ри/дя­ди/те­ти/феи и пр. фоль­к­лор­ные эле­мен­ты)
    В ро­ли крес­т­ных выс­ту­па­ют ин­тер­фей­сы. Их у каж­до­го клас­са мо­жет быть сколь­ко угод­но, и для то­го, что­бы иметь крес­т­ных не обя­за­тель­но иметь ро­ди­те­лей. Ин­тер­фей­сы все очень стро­гие - они точ­но зна­ют что дол­жен уметь де­лать их крес