C Sharp синтаксис

Статията описва синтаксиса на програмния език C#. Описаните характеристики са съвместими с .NET Framework и Mono.
В C# имената на променливи, методи, класове и други обекти се наричат идентификатори. Те се избират от разработчика. Идентификаторите могат да съдържат букви, цифри ([0 – 9]) и символа долна черта (_), но не могат да започват с цифра или символ, освен ако не е ключова дума. Идентификаторите са чувствителни към големи и малки букви (FOO е различно от foo). Те не могат да се използват като запазени ключови думи, освен ако не са оградени с @. Това включва думи като int, class, public и т.н.
Идентификаторите могат да се именуват, използвайки следните конвенции: PascalCase за имената на класове, методи, и публични свойства (FirstName, LastName), и camelCase – за имената на локални променливи и параметри на методи (firstName, lastName).
Ключовите думи са предефинирани запазени идентификатори, които имат специално значение за компилатора. Езикът C# има да вида ключови думи – контекстуални и запазени. Запазените ключови думи, като false или byte, могат да бъдат използвани само като ключови думи. Контекстуалните ключови думи, като where или from, могат да бъдат третирани като такива само в определени ситуации.
Контекстуалните ключови думи имат специфично значение, само в ограничен програмен контекст, и могат да бъдат използвани като идентификатори извън този контекст. Някои контекстуални думи, като partial или where, имат специално значение в два или повече контекста.
Употребата на @-ключови думи за идентификатори не се препоръчва, освен за специални цели. Също знакът @ може да предхожда всеки идентификатор, но това се счита за лоша практика.
Литералите са константни стойности, зададени в кода на компютърната програма. Те представляват данни в техния основен вид, които могат да се присвояват на променливи или да се използват в изрази. Пример за това се явява числото 123 (целочислен литерал), текстът изучаваме C# (текстов литерал) или буквата „ы“ (символен литерал). Всеки литерал има определен тип (или се отнася към определен тип) и се обработва в съответствие с правилата за работа със стойностите от дадения тип. Символният литерал се затваря в единични кавички, а текстовият – в двойни.
Булевите литерали са true или false. Когато присвояваме стойност на променлива от тип bool, можем да използваме единствено някоя от тези две стойности или израз от булев тип (който се изчислява до true или false).
Целочисленият литерал се реализира като стойност от най-малкия тип, започвайки от тип int, достатъчен за съхранението на стойността на литерала. Целите числа могат да бъдат представяни в десетичен, шестнадесетичен и двоичен формат. При тях се използват още следните означения:
Реалните числови литерали (например числото 12.3) се реализират във вид на стойности от типа float, double и decimal. При тях се използват още следните означения:
Символните литерали представляват единични символи, оградени в единични кавички (например 'F' или 'я') и се реализират като стойности от тип char. Стойността на символните литерали може да бъде:
Текстовите литерали (низови) се затварят в двойни кавички (например "Езикът C#" или "А") и се реализират като обекти на класа String от пространството от имена System (използваме идентификатора string за израза System.String).
Цитираните текстови литерали се използват най-често при задаване на имена на пътища във файловата система.
Символните последователности са литерали, които представляват последователност от специални символи, които задават символ, който по някаква причина не може да се изпише директно в програмния код. Всички последователности започват с обратно наклонен черта \, която наричаме екраниращ символ (escaping character).
Нулевите типове (nullable types) представляват нулева стойност, която означава, че променливата не сочи към никакъв обект. Те са инстанции на структурния декоратор (Wrapper) на System.Nullable върху примитивните типове.
Нулевите типове могат да представляват неопределена или липсваща стойност за променлива, която обикновено не може да бъде null. Тези типове са полезни при работа с бази данни или други структури, които имат нулева стойност по подразбиране.
За проверка дали нулевият тип съдържа стойност, може да се използва метода HasValue или директна проверка за null.
Разделителите за цифри разделят цифрите в числови стойности със символа за долна черта _ за по-голяма четимост и разбиране на програмния код.
Променливите са именувани пространства (идентификатори) в паметта, които могат да съхраняват стойности от различни типове данни. Всяка променлива има свой тип, който влияе на обема на паметта, заделяна за променливата, а също така и на това какви стойности могат да бъдат записвани в променливата и какви операции могат да се изпълняват с променливата.
Променливите имат обхват (scope), който определя къде в кода могат да бъдат достъпвани. Обхватът на променливите се ограничава от блоковете, където са декларирани.
Преди да бъде използвана променливата, тя трябва да бъде декларирана. Декларирането може да стане практически на всяко място в програмния код, но обезателно преди променливата да бъде използвана. При деклариране на променлива може веднага да ѝ бъде присвоена стойност.
Присвояването на стойност на променлива представлява задаване на стойност, която да бъде записана в нея. Тази операция се извършва чрез оператора за присвояване =. От лявата страна на оператора се изписва име на променлива, а от дясната страна – новата ѝ стойност.
Инициализация означава задаване на начална стойност в момента на тяхното деклариране. Всеки тип данни в C# има стойност по подразбиране (инициализация по подразбиране), която се използва, когато за дадена променлива не бъде изрично зададена стойност.
Няколко променливи от един и същи тип могат да бъдат декларирани и инициализирани в една декларация.
Възможно е и динамично инициализиране на променливата, когато променливата се инициализира въз основа на израз, в който влизат други променливи, ако към момента на изчисление променливите имат стойности.
Декларирането на променливи с var позволява спецификатора на декларираната променлива да бъде заменен с ключовата дума var, ако действителният ѝ тип може да бъде статично определен от инициализатора. Това намалява повторенията, особено за типове с множество типови параметри.
Константите са имплицитно статични стойности, които не могат да бъде променяни след декларирането им.
Константите трябва да бъдат декларирани в контекста като поле или локална променлива с префикса на ключовата дума const, като стойност ѝ трябва да се даде при декларирането. След това тя е заключена и не може да бъде променяна.
Следният пример показва всички употреби на const:
Ключовата дума readonly прави подобно нещо за полетата. Подобно на полетата, маркирани с const, те не могат да се променят веднъж щом им се зададе начална стойност. Разликата е, че може по избор да им се зададе начална стойност в конструктора. Това важи само за полета, които са само readonly и могат да бъдат, както членове на една инстанция или статични членове на класа.
Операторите с къдрави скоби { ... } се използват за обозначаване на блок код и нова област на видимост. В този програмен блок може да се намират членовете на класа или тялото на метод.
В тялото на метод скобите може да се използват за създаване на нови обхвати:
Името на променлива, декларирана във вътрешен блок, не може да съвпада с името на променлива, декларирана във външен блок.
C# приложенията се състоят от класове и техните членове. Класовете и другите типове съществуват в именни пространства, но също могат да бъдат вложени в други класове.
Методът Main е входната точка за изпълнение на всяка конзолна програма. Това е мястото, където започва изпълнението на програмата. Може да съществува само един главен метод в клас и той е статичен. Методът обикновено връща void и като списък от параметри има единствен параметър от тип масив от низове.
Главният метод има специфичен синтаксис и може да приема аргументи от командния ред.
Ако Main() метода не е написан по някой от указаните начини, програмата ще се компилира, но няма да може да се стартира, защото не е дефинирана правилно нейната входна точка.
В главния метода е позволено връщането на стойност на цяло число, ако има такава.
Асинхронните операции могат да бъдат изчаквани директно в Main метода, като се декларира връщащ тип на Task.
Всички комбинации на Task или Task<int>, със или без параметъра string[] args са поддържани.
Програмите от най-високо ниво (top-level statements) са нова функционалност, въведена в C# 9.0, която позволява на разработчиците да пишат програмен код без да се налага дефиниране на класове и методи за входна точка.
Това опростява писането на малки програми и скриптове, като премахва някои от основните шаблони.
Именните пространства (namespaces) представляват организирани логически групи от класове, интерфейси, структури, делегати и други типове. Именните пространства са част от името на типа и се използват за групиране и/или разграничаване на посочените структури от други такива.
Ключовата дума namespace се използва за деклариране на обхват, който съдържа множество от свързани обекти. Именните пространства може да се използват за организиране на елементите на кода и да се създадат глобално уникални типове.
Именните пространства имплицитно са с публичен достъп и това не може да се променя.
В именните пространства може да се декларира един или повече от следните типове:
Именните пространства могат да бъдат вложени едно в друго (.), за да осигурят още по-добра организация.
C# и .NET Framework предоставят множество стандартни именни пространства, които съдържат различни основни класове и функционалности. Някои от тях включват:
Декларацията using зарежда конкретно именно пространство (namespace) от референтни събрания (асемблита). Обикновено се поставя в горната част (или заглавието) на кода, но може да бъде поставена на друго място, по желание, например вътре в класовете.
Using декларацията също може да се използва за определяне на друго име за съществуващо именно пространство или тип. Това е особено удобно, когато имената са твърде дълги и трудно разбираеми.
Директивата using static позволява зареждане на статични членове на класове, така че да се използват директно, без да се споменава името на класа.
Операторите позволяват обработка на примитивни типове данни и обекти. Те приемат един или няколко операнда и връщат някаква стойност.
Операторите в C# са специални символи и извършват специфични преобразувания над един, два или три операнда.
Всички бинарни оператори са ляво-асоциативни, освен операторите за присвояване на стойност. Всички оператори за присвояване на стойности и условните оператори ?: и ?? са дясно-асоциативни. Унарните оператори нямат асоциативност.
Използване на оператори:
В езика C# някой оператори имат приоритет над други. Операторите с по-висок приоритет се изчисляват преди тези с по-нисък.
Скобите имат най-висок приоритет. Те могат да се използват за промяна на приоритетите на другите оператори. При писане на по-сложни изрази или такива с повече оператори, е препоръчително слагането на скоби, за да се избегнат трудности при четене и разбиране на кода. Добра практика е използването на скоби, дори да не изглежда необходимо.
Възможно е някои оператори да бъдат овърлоудвани (претоварени) чрез овърлоуд метода.
Операторът за кастване не може да се претоварва, но е възможно да се напише метод за преобразуване с целевия клас. Методите за преобразуване могат да дефинират два вида оператори за преобразуване: имплицитни (явни) и експлицитни (неявни).
Операторът за имплицитно преобразуване (implicit typecasting) преобразува един тип в друг автоматично, без изрично да се използва какъвто и да е оператор (( )). Употребата на имплицитни оператори са позволени, когато няма възможност от загуба на данни. Такова конвертиране е от тип с по-малък обхват към тип с по-голям обхват (int към long) или когато имаме няколко типа данни с различен обхват.
Възможни неявни конверсии са:
Операторът за експлицитно преобразуване (explicit typecasting) на типове се използва винаги, когато има вероятност за загуба на данни. Подобни загуби са възможни при конверсия на реални към целочислени типове. Или при конверсия от тип с по-голям обхват към тип с по-малък (double към float или long към int).
Операторът as ще се опита да направи мълчаливо кастване към даден вид. Ако преобразуването е успешно, операторът ще върне обекта като нов тип, ако не е успешно, ще върне нулева референция.
Null-коагулиращия оператор ?? се използва за определяне на стойността по подразбиране за двата nullable типа стойност и референтни типове. Операторът връща операнда от лявата страна ifNotNullValue, ако стойността не е нула. В противен случай той връща операнда от дясната страна otherwiseValue.
Следният код
е същият като
В C# 8.0. са въведени Null-коагулиращи присвоявания.
Това е еквивалент на следният код.
С# наследява голяма част от конструкциите за контрол от C/C++, а също и добавя нови такива, като foreach.
Тези конструкции определят начина на изпълнение на програмата по зададени условия.
if конструкцията се изпълнява, когато зададеното условие е вярно, т.е. има стойност true. Ако в тялото на условната конструкция има само един оператор, не се изискват блок скоби, въпреки че е препоръчително да се слагат блок скоби.
Конструкция с един оператор:
Конструкция с else блок (без скоби):
Използването на поредица от if конструкции:
Switch конструкцията служи като филтър за различни стойности. Всяка стойност довежда до „случай“ – case етикет. Не е позволено отпадане на случаи и затова обикновено се използва ключовата дума break за край в case етикет. Неусловно return в case блок също може да се използва за край. Може да се разгледа как goto инструкция може да се използва, за преминаване от един case етикет към друг. Много case етикети може да доведат до еднакъв код обаче. Case етикетът – по подразбиране изпълнява всички други случаи, които не се обработват от конструкцията.
Итерационни структури са конструкции, които са многократно изпълнявани, докато е в сила дадено условие.
while цикълът се използва, когато искаме да повтаряме извършването на определена логика, докато е в сила дадено условие. Когато условието стане грешно, while цикълът прекъсва изпълнението си и програмата продължава с изпълнението на останалия код след цикъла.
По структура, do ... while наподобява while. В конструкцията на do-while цикъла, условието винаги се проверява след тялото му, което от своя страна гарантира, че при първото завъртане на цикъла, кодът ще се изпълни, а проверката за край на цикъл ще се прилага върху всяка следваща итерация на do-while.
for цикълът се състои от три части: деклариране, условие и обновяване на променлива. Всяка от тях може да не бъде взета предвид, тъй като не са задължителни.
Това е еквивалент на следната while конструкция.
foreach цикълът произлиза от for цикъл и следва определен модел, описан в спецификацията на езика С# за обхождане на всички елементи.
Jump конструкциите са наследени от C/C++ и са в основата на асемблираните езици. Те представляват jump инструкция – незабавно променяне хода на изпълнение на програмата.
Етикетите са определени точки в кода, към които може да се промени хода на изпълнение на програмата с помощта на goto инструкция.
goto инструкцията може да се използва в switch конструкция за прехвърляне контрола от един към друг case етикет.
break инструкцията прекратява най-близко обхващащия цикъл или switch конструкция. Управлението се предава на конструкцията, която следва след приключената, ако има такива.
continue инструкцията прекъсва текущата итерация на текущата конструкция за управление и започва следващата итерация.
while цикълът в кода по-горе чете символи с метода GetChar() и ако символът е празно пространство, ще се прескочат останалите инструкции в тялото на цикъла.
Методът за прихващане на изключения по време на изпълнение на програмата в С#, е наследен от Java и C/C++.
Класът System.Exception е базовият клас за всички изключения. Обектът изключение съдържа цялата информация за точно определено изключение, както и обвито изключение, ако определеното изключение съдържа причина за възникването му. Програмистите могат да определят свои собствени изключения, като произлязат от класа Exception.
Изключение може да бъде прихванато по следния начин:
Изключенията се управляват в try ... catch блок.
Кодът в try блока се изпълнява и ако се появи изключение, изпълнението на блока се прекъсва и изключението се прихваща в catch блока. Може да има много catch блокове, като в този случай се изпълнява първия блок с дефинирана променлива за изключение, чиито тип съвпада с типа на хвърленото изключение.
Ако няма catch блок, който да съответства на типа на хвърленото изключение, изпълнението на външния блок (или метод), съдържащ try...catch конструкцията се прекъсва и изключението се предава извън съдържащия блок или метод. Може да има много catch блокове, като в този случай се изпълнява първия блок с дефинирана променлива за изключение, чиито тип съвпада с типа на хвърленото изключение. Изключението се препредава, докато не намери съвпадение в някой от текущо активните методи. Ако изключението достигне главния метод без съвпадение, тогава цялата програма се прекратява и текстово описание на изключението се изписва на стандартния изходен поток.
Кодът в finally блока се изпълнява винаги след try и catch блоковете, независимо дали е прихванато изключение. Такива блокове са полезни за осигуряване на почистващи кодове.
Или catch блок, или finally блок, или и двата трябва да следват try блок.
C# е статично типов език като C и C++. Това означава, че на всяка променлива или константа трябва да се зададе тип, когато се декларира. Типовете данни в C# са два вида: стойностни и референтни.
Стойностните типове се съхраняват в стека, съдържат директно своята стойност и се предават по стойност. С други думи, ако се декларира променлива от стойностен тип, то стойността се съхранява директно в паметта. Стойностните типове се освобождават при излизане от обхват – стойността на декларираната променлива се изтрива от стека.
Структурите са по-известни като structs. Структурите са дефинирани от потребителя стойностни типове, които се декларират със запазената дума struct. Те са много подобни на класовете, но са по-ограничени. Някои важни синтактични разлики между клас и структура са представени в раздел Разлики между класове и структури.
Всички примитивни типове данни са структури.
Те са примитивни типове данни.
Забележка: string (System.String) не е структура, както и не е примитивен тип.
Изброените типове (enum) са наименувани константи, представящи цели числа.
Променливите от изброен тип имат стойност по подразбиране нула. Те могат да бъдат декларирани или инициализирани към наименувана константа, определена от изброения тип.
Променливите от изброен тип имат целочислени стойности. Събиране и изваждане между променливи от един и същ тип е разрешено без конкретно преобразуване, но операциите умножение и деление са малко по-рисковани и изискват изрично (явно) преобразуване. Преобразуване се изисква също и при превръщане на променливи от изброен тип към и от целочислени типове. Въпреки това преобразуването няма да хвърли изключение, ако стойността не е зададена от дефиницията на изброения тип.
Стойностите могат да бъдат съединени с помощта на побитовия оператор OR
Променливи референтен тип имат строго типизиран указател (адрес на клетка от паметта). Когато конструкторът е извикан, се създава обект в динамичната памет и указател към променливата, т.е. референтните типове съдържат в стека указател към динамичната памет, където се съхранява тяхната стойност. Когато променливата на обект излиза от обхват указателят се разкача от стойността си, и когато няма указател, свързан към обекта, то той остава свободен и се маркира като боклук. По някое време системата за почистване на паметта ще изтрие тези обекти.
Променлива от референтен тип приема стойност null, когато няма указател към обект.
Типът масив е референтен тип, който се отнася за група от един или повече елементи от един и същ тип. Всички типове масиви произхождат от общ базов клас System.Array. Всеки елемент е посочен от своя индекс, както е при C++ и Java. Индексът представлява цяло число. Размерността на масива се определя от броя на индексите, които трябва да бъдат указани за идентифициране на елемент от масива. Под размер на масива за дадена размерност се има предвид броят на стойностите, които може да приеме индексът, съответстващ на дадената размерност. Индексацията винаги започва от 0. Размерността на даден масив може да бъде определена с помощта на свойството Rank.
Масив в C# е това, което се нарича динамичен масив при C++.
Масивите имат удобен синтаксис за инициализация на масиви.
Масивите могат да имат различни размерности, например двумерните масиви представляват матрица.
Класовете са самостоятелно описващи се, дефинирани от потребителя референтни типове. По същество всички типове в .NET Framework са класове, включително структури и изброени типове, които са генерирани от компилатора класове. Елементите на класа са private по подразбиране, но могат да бъдат декларирани като public, за да се виждат извън класа или protected, за да се виждат, от който и да е от наследниците на класа.
Класът System.String или накратко string е неизменима последователност от символи char), представени в Unicode таблицата.
Действия, извършени върху променлива от тип string, винаги ще връща нова такава.
Класът System.StringBuilder може да се използва, когато се налага променяне на символни низове.
Интерфейсът е структура от данни, която съдържа дефиниция на роля (на група абстрактни действия) без действително осъществяване. Променлива на интерфейсен тип е указател към инстанция на клас, който имплементира този интерфейс. Вижте #Интерфейси.
C# предоставя типово-безопасни, обектно-ориентирани указатели към функции под формата на делегати.
Инициализиране на делегат като анонимен метод.
Инициализиране на делегат като ламбда израз.
Събитията са указатели, които могат да сочат към многобройни методи. По-точно те обединяват тези указатели към един идентификатор. Това може да се разглежда като разширение на делегатите. Събитията в C# са специални инстанции на делегати, декларирани с ключовата дума event. Те обикновено се използват като тригери в UI разработването. Формата, използвана в C# и други езици от Common Language Infrastructure, се основава на това в класическия Visual Basic.
Събитието обикновено е придружено от метод, който ще манипулира събитие. Този метод представлява специален делегат, който има 2 параметъра – изпращач и аргументи на събитието. Типът на аргумент-обекта на събитието произхожда от класа EventArgs, който е част от базовата библиотека на CLI.
Веднъж декларирано в класа си, единствения начин да се извика събитие е от притежателя, т.е. от класа, в който е дефинирано. Методът за получаване на събитие може да бъде имплементиран извън, за да може бъде стартиран, когато събитието е изгубено.
Възможна е и реализация на къстъмизирано събитие:
Нулевите типове са представени в C# 2.0. първоначално, за да се позволи стойностните типове да приемат стойност null, когато се работи с база данни.
Всъщност това е същото като използването на Nullable<T> структура.
C# има и позволява указатели към стойностни типове (примитивни, enums и structs) в не безопасен контекст: методи и блок с код, маркирани с unsafe. Синтактично са същите като указателите в C и C++. Прекъсване по време на компилиране е изключено в не безопасен контекст.
При използването на структури се изискват само структури без елементи от референтен тип като символен низ или друг клас.
Начин на използване:
Типът dynamic позволява динамични справки за С# по време на компилиране. Dynamic е статичен тип, който съществува само по време на компилиране.
Анонимните типове са безименни класове, които се генерират от компилатора. Използват се за еднократна употреба. Те са много полезни в случаи, когато при LINQ заявка, която връща резултат под формата на обект с select трябва да върнат специални стойности. След това може да се определи анонимен тип, съдържащ автоматично генерирани, само за четене полета за тези стойности.
При инстанциране на друга декларация от анонимен тип със същата сигнатура, типът се подразбира от компилатора.
Опаковането е операция за преобразуване на променлива от стойностен тип в обектен тип, който представлява референтен тип. Опаковането в C# е неявно преобразуване.
Разопаковането е операция за преобразуване на променлива от референтен тип (преди това трябва да е изпълнена операция Опаковане) в стойностен тип. Разопаковането в C# изисква изрично (явно) преобразуване.
C# има директна поддръжка за обектно-ориентирано програмиране.
Обектът е създаден по шаблона на даден клас и е инстанция на този клас.
В C# обектите са или указатели, или стойности. Не се обособяват други синтактични особености между тях в кода.
Всички типове, дори и стойностните типове в опакована форма, неявно наследяват System.Object класа, който е основният базов клас на всички обекти. Класът съдържа най-общите методи, споделяни от всички обекти. Някои от тях са виртуални и могат да бъдат отменени.
Класовете наследяват System.Object пряко или непряко чрез друг базов клас.
Основни методи на Object класа са:
Класовете са основите на един обекто-ориентиран език, какъвто е C#. Те служат като шаблон, чрез който се описват обекти. Съдържат компоненти, които съхраняват и манипулират данни по реалистичен начин.
Въпреки че класовете и структурите са подобни, както по начина на деклариране, така и по това как се използват, има някои съществени разлики. Класовете са референтен тип, а структурите – стойностен тип. Структурата се съхранява в стека, когато се декларира и съдържа директно стойността си. Класовете са различни, защото съдържат в стека указател към динамичната памет, където се съхранява стойността. Указателят има тип и може да съдържа само обекти от своя тип.
Структурите изискват малко повече писане, отколкото класовете. Например трябва изрично да се създаде конструктор по подразбиране, който не изисква аргументи, за да се инициализира структурата и нейните елементи. Компилаторът ще създаде такъв по подразбиране за клас. Всички полета и свойства на една структура трябва да бъдат инициализирани преди да се създаде инстанция. Структурите не притежават финализатори и не могат да наследяват от други класове, както е при класовете. Въпреки това, те наследяват от System.ValueType, който на своя страна наследява System.Object. Структурите са по-подходящи за малки конструкции от данни.
Това е кратко обобщение на разликите:
Клас се декларира по следния начин:
Частичен клас е деклариране на клас, чиито код е разделен в отделни файлове. Различните части на частичния клас трябва да бъдат дефинирани с ключова дума partial.
Преди да може да се използват елементите на класа, трябва да се инициализират променливите с указател към обект. За да се създаде обект, трябва да се извика съответния конструктор, използвайки ключова дума new. Конструкторът има същото име, както на класа.
За структурите не е задължително да се извиква конструктор, защото той се извиква автоматично по подразбиране. Трябва само да се декларира и констукторът се инициализира със стандартни стойности.
Предлага се по-удобен начин за инициализиране на публични полета и свойства на даден обект. Извикването на конструктор не е задължително, когато има такъв по подразбиране.
Инициализаторите на колекции имат синтаксис, подобен на този на масивите за инициализиране на колекции. Компилаторът ще генерира само извикване на Add-method. Това се отнася за класове, които имплементират ICollection интерфейс.
Елементите на дадена инстанция и статичните компоненти на класа са достъпни чрез използването на . оператор.
Елементите на дадена инстанция са достъпни чрез името на променлива.
Статичните компоненти са достъпни чрез използването на името на класа или друг тип.
В не безопасен контекст, членовете на стойността (структурен тип) указани чрез указател, са достъпни чрез -> оператор, точно както при C и C++.
Модификаторите са ключови думи, които се използват за изменение в декларирането за типове. Особено забележимо е наличието на подгрупа, съдържаща модификатори за достъп.
Статичният модификатор определя, че компонент принадлежи на класа, а не на конкретен обект. На класовете, отбелязани като статични е позволено да съдържат само статични компоненти. Статичните компоненти понякога се приемат като компоненти на класа, тъй като те се отнасят към класа като цяло, а не към своите инстанции.
Модификаторите за достъп, или модификатори при наследяване, определят достъпността на класовете, методите и другите компоненти. Компонент, дефиниран с public може да бъде достъпен от всякъде. Private компонентите могат да бъдат достъпни само от класа, в който са декларирани и ще бъдат скрити при наследяване. Компоненти с модификатор за достъп protected ще бъдат невидими, но достъпни при наследяване. Класове и членове с модификатор на достъп internal ще са достъпни само от ползватели на едно и също асембли.
Класовете и структурите са изрично internal, а компонентите – private, ако нямат зададен модификатор за достъп.
Следната таблица показва къде могат да бъдат използвани модификаторите за достъп.
Конструкторът е специален метод, който се извиква автоматично, когато се създава обект. Неговата цел е да инициализира елементите на обекта. Конструкторите имат същото име като на класа и не връщат нищо. Могат да приемат параметри като всеки друг метод.
Конструкторите могат да бъдат public, private или internal.
Деструкторът се вика, когато обектът ще се събира от системата за почисване на паметта, за да се извърши ръчно почистване. Има метод деструктор по подразбиране наречен finalize (финализатор), който може да се пренапише чрез деклариране на самия себе си.
Синтаксисът е подобен на този на конструкторите. Разликата е в това, че името се предхожда от ~ и не може да съдържа никакви параметри. Не може да има повече от един деструктор.
Финализаторите са винаги private.
Както в C и C++, има функции, които групират многократно използван код. Основната разлика е, че тези функци, точно както е при Java, трябва да пребивават в рамките на класа. По тази причина функцията се нарича метод. Методът има връщана стойност, име и обикновено някакви параметри, които се инициализират, когато методът се извиква с аргументи. Може или да принадлежи на инстанция на клас или да бъде статична компонента.
Метод се извиква с . нотация на зададената променлива, или при статични методи – име на типа.
Единствената разлика между вложените методи и обикновените методи е, че вложените методи не могат да бъдат static.
Аргументи могат да се предават по референция, когато при извикване на метод към описанието на параметър в дефиницията на метода се добави ключова дума ref или out. Удобно е да се използват, когато се налага в даден метод стойността на променлива да се променя по референция, т.е. тази промяна директно се отразява на променливата. Разликата между тези два параметъра е, че преди инициализацията при out достъпът е само за писане, докато при ref достъпът е за четене и писане.
C# 4.0 въвежда незадължителни параметри със стойности по подразбиране, както е в C++.
Незадължителните параметри позволяват пропускането на параметри при извикване на метод. Освен това е възможно подаване на стойност на параметър чрез името му (именуван параметър) при извикване на метода, което позволява селективно преминаване през всяко подмножество от незадължителни параметри. Единственото ограничение е, че именуваните параметри трябва да бъдат поставени след неименуваните параметри. Именуваните параметри могат да определят незадължителните и задължителните параметри, както и да бъдат използвани за произволна наредба на аргументи при извикване на метода.
Незадължителните параметри правят по-лесна съвместната работа с COM. По-рано, трябваше да се премине през всеки параметър в метода на COM компонента, дори и през тези, които не са задължителни.
С незадължителните параметри, кодът може да се съкрати така:
Опция на C#, е способността да се извиква собствен код. Сигнатурата на метода е проста, методът се декларира без тяло и е дефиниран с extern. Атрибутът DllImport също трябва да се добави към указателя на дадения DLL файл.
Полетата са променливи, които се декларират в класа и съхраняват данни
Полетата могат да бъдат инициализирани директно, когато се декларират (освен, когато се декларират в структура).
Свойствата имат синтаксис като този на полетата и възможностите на методите. Свойството може да има два метода за достъп: get и set.
Модификаторите за методи за достъп до свойство по подразбиране са наследени от свойството. Трябва да се има предвид, че модификаторът за методи за достъп може да бъде само или еднакъв, или ограничен от модификатора за свойството.
Тази опция на C# 3.0 включва автоматично имплементирани свойства. Дефинират се методи за достъп до свойство без тяло и компилаторът ще генерира полета и необходимия код за тези методи.
Индексаторите добавят възможностите на индексирането при масиви на обектите. Те се имплементират по начин подобен на свойствата.
Класовете в C# могат да наследяват само от един клас. Класът може да произхожда от кой да е клас, който не е дефиниран с sealed.
Методи, които са дефинирани с virtual се изпълняват, но те могат да бъдат пренаписани от наследниците с ключовата дума override.
Имплементацията се определя по действителния тип на обекта, а не по типа на променливата.
При вариант на невиртуален метод с друга сигнатура, може да се използва ключова дума new. Методът, който ще се използва, ще се определи по тип на променливата, вместо по действителния тип на обекта.
Това показва примера:
Абстрактните класове са класове, които служат само като шаблони и няма да може да се инициализира обект от този тип. В противен случай са точно като обикновен клас.
Може да има също абстрактни компоненти. Абстрактните компоненти са компоненти на абстрактни класове, които нямат никаква имплементация. Те трябва да бъдат пренаписани от класа, който наследява компонента.
Модификаторът sealed може да се комбинира с други като незадължителен модификатор за класове, за да ги направи ненаследими.
Интерфейсът (interface) е абстрактен тип, който съдържа само декларации на методи, свойства, събития или индексатори, но не и техните имплементации. Класовете или структурите, които имплементират интерфейс, трябва да предоставят имплементация за всички членове на интерфейса. Те са полезни, когато трябва да се определи договор между компоненти с различни типове, които имат различни имплементации. Интерфейсните компоненти са public. Интерфейсът може да бъде имплементиран неявно (explicit) или явно (implicit).
Интерфейсът се имплементира от клас или като разширение към друг интерфейс по същия начин, по който произлиза клас от друг клас с помощта на : нотация.
Когато интерфейс се имплементира неявно (explicit implementation), неговите компоненти трябва да бъдат public.
Начин на използване:
При явна имплементация (implicit implementation) могат да се имплементират едновременно няколко интерфейса, които съдържат методи с еднаква сигнатура. Може също така компонентите да се имплементират явно, които са достъпни само, когато обектът се използва като интерфейс.
Начин на използване:
Свойствата в класа, който обхваща IBinaryOperation са автоматично имплементирани от компилатора и полето е автоматично добавено (виж #Автоматични свойства).
Интерфейсите и класовете позволяват да се разширяват от множество интерфейси.
Това е интерфейс, който е разширен от два интерфейса.
Интерфейсите и абстрактните класове са подобни. Ето по какво се различават:
Шаблонните типове (или параметризирани типове) са мощна характеристика, която позволява създаването на методи и класове, в които не се конкретизира типа данни до момента на инстанциране на класа или метода.
Декларирането на шаблонен тип от класа Generic става със задаване на типа данни в счупените скоби (<...>).
Където Т е типа данни.
Колекцията List<T> е динамичен масив, който няма фиксиран размер и позволява директен достъп по индекс на елементите му.
List<T> се реализира чрез използването на параметризирани типове, което означава, че типът на елементите се определя, когато се създава списъкът.
Основни методи на класа List<T>:
Свързаните списъци (LinkedList<T>) представляват колекция от списъци свързани по между си. Те ни дават възможност да поставяме елементи на желани от нас позиции.
Основни методи на класа LinkedList<T>:
Стекът (Stack<T>) представлява структура от данни от тип „последният влязъл, първи излиза“ (Last In, First Out). При структурите от данни от тип Stack<T> липсва индексатор и затова имаме достъп само до последния елемент, който е влязъл.
Основни методи на класа Stack<T>:
Опашката (Queue<T>) представлява структура от данни от тип „първият влязъл, първи излиза“ (First In, First Out). При структурите от данни от тип Queue<T> липсва индексатор и затова имаме достъп само до първия елемент, който е влязъл.
Основни методи на класа Queue<T>:
HashSet<T> представлява колекция от уникални елементи.
Основни методи на класа HashSet<T>:
Интерфейсите на класа Generic (generic interfaces) предоставят шаблони типове, които могат да работят с различни типове данни.
В .NET Framework има няколко вградени интерфейса:
Типовете данни Т (параметризирани типове) са имена, които заменят конкретен тип данни при създаването на нов обект от класа Generic. Те могат да бъдат обвързани с класове, или методи чрез поставяне на типа Т в счупените скоби <...>. След декларацията на класа, или метода може да се извиква и подаде конкретен тип данни.
Ковариантността и контравариантността са концепции на класа Generic, които определят как типовете могат да бъдат заменяни с по-общи или по-специфични типове. Тези концепции се прилагат при интерфейси и делегати.
Например, съществуващият интерфейс IEnumerable<T> се задава, както следва:
Следователно, всеки клас, който имплементира IEnumerable<Derived> за класа Derived, също се счита за съвместим с IEnumerable<Base> за всички класове и интерфейси на Base, които Derived разширява, пряко или непряко.
За контравариантност, съществуващият интерфейс IComparer<T> се задава, както следва:
Следователно, всеки клас, който имплементира IComparer<Base> за класа Base, също се счита за съвместим с IComparer<Derived> за всички класове и интерфейси на Derived, които са разширени от Base.
Енумераторът представлява итератор. Енумераторите се използват чрез извикване на метода GetEnumerator() на обект, който имплементира IEnumerable интерфейс. Контейнерните класове обикновено реализират този интерфейс. Въпреки това, операторът foreach може да работи с всеки обект, предоставящ такъв метод, дори ако не имплементира IEnumerable интерфейс.
Примера показва проста употреба на итератори в С# 2.0.
.NET 2.0 Framework позволява на C# да представи итератор, който има функционалността на генератор чрез конструкцията yield return, подобна на yield в Python.С yield return, функцията автоматично запазва състоянието си по време на итерацията.
LINQ, съкратено от Language Integrated Queries, представлява редица разширения на .NET Framework, които включват интегрирани в езика заявки и операции върху елементи от даден източник на данни (най-често масиви и колекции). LINQ обработва колекциите по подобие на SQL езиците, които обработват редовете в таблици в база данни. Той е част от C# и VisualBasic синтаксиса и се състои от няколко основни ключови думи.
С ключовите думи from и in се задават източникът на данни (колекция, масив и т.н.) и променливата, с която ще се итерира (обхожда) по колекцията (обхождане по подобие на foreach оператора).
Разликата от SQL е мястото на ключовата дума from. В SQL е на последно място, а в C# е на първо. Това е така заради синтаксиса на C#.
Анонимните методи, или в настоящата си форма по-често цитирани като „ламбда изрази“, са функционалност, която позволява да се пишат функции, подобни на затварящи, директно вградени в кода.
Съществуват различни начини за създаване на анонимни методи. Преди въвеждането на C# 3.0 има ограничена поддръжка, чрез използването на делегати.
Анонимни делегати са указатели към функции, които съдържат анонимни методи. Целта е да се улесни използването на делегати чрез опростяване процеса на възлагане на функцията. Вместо да създава отделен метод, програмистът, използвайки правилния синтаксис, може да напише функцията директно в кода си, а компилаторът ще генерира анонимна функция за него.
Ламбда изразите представляват анонимни функции, които съдържат изрази или последователност от оператори. Всички ламбда изрази използват ламбда оператора =>. Лявата страна на ламбда оператора определя входните параметри на анонимната функция, а дясната страна представлява израз или последователност от оператори, която работи с входните параметри и евентуално връща някакъв резултат.
Обикновено ламбда изразите се използват като предикати или вместо делегати (променливи от тип функция), които се прилагат върху колекции, обработвайки елементите от колекцията по някакъв начин и/или връщайки определен резултат.
Телата на ламбда изрази, съдържащи повече от един израз, са оградени от скоби. Вътре в скобите, кода може да бъде написан като стандартен метод.
Ламбда изразите могат да бъдат подавани и като аргументи при извикването на метод, подобно на анонимни делегати, но с по-естетичен синтаксис.
Ламбда изразите по същество са методи, генерирани от компилатора, които се подават чрез делегати. Тези методи са запазени само за компилатора и не могат да се използват в друг контекст.
Допълващите (разширяващи) методи са форма на т.нар. „синтактична захар“. Те създават илюзията за добавяне на нови методи към съществуващ клас извън неговото дефиниране. На практика, допълващият метод е статичен метод, който може да се извика по същия начин, както и инстантния метод. Извикващия метод е обвързан с първия параметър на метода, обозначен от ключовата дума this.
Локалните функции могат да се дефинират във всеки един друг метод, конструктор или get и set свойства. Локалните функции имат достъп до променливи, които се използват в съдържащия ги метод. Модификаторите за достъп (public, private, protected) не могат да се използват с локални функции. Локалните функции не поддържат предефиниране на функции, т.е. не може да има две локални функции в един и същи метод с едно и също име, дори и ако сигнатурите на двете функции се различават по списъка от параметри (броят на елементите в него или подредбата им). Когато компилаторът компилира такива функции, те биват превърнати в private static методи.
В примера методът Sum() е локална функция, тъй като е вложен в метода Main(), т.е. Sum() е локален за Main(). Това означава, че методът Sum() може да бъде използван само в метода Main(), тъй като е деклариран в него.
C# изпълнява затварящи блокове чрез използването на using конструкцията. Using конструкцията приема израз, чийто резултат е обект, включващ IDisposable. Компилаторът генерира код, който гарантира затварянето (унищожаването) на обекта, когато кода, в обхвата на using конструкцията, бъде изпълнен. Използването на using конструкцията е „синтактична захар“. Тя прави кода по-четим от еквивалентния му блок try ... finally.
Lock конструкцията в C# е още един пример за ползите от „синтактичната захар“. Той действа чрез маркирането на блок от кода като критична зона като взаимно изключва достъпа до посочения обект. Подобно на using конструкцията, lock работи като компилатора генерира try ... finally блок на негово място.
Атрибути (характеристики) са парчета информация, която се съхранява като метаданни в компилирания пакет. Атрибутът може да бъде добавен към видове и участници (например свойства, методи). Атрибутите могат да се използват за по-добро управление на пред-процесорните инструкции.
.NET Framework идва с предварително зададени атрибути, които могат да бъдат използвани. Някои от тях играят важна роля по време на изпълнението на програмата, докато други са просто синтактична украса в кода (например CompilerGenerated – само маркира, че е елемент, генериран от компилатора). Също така могат да бъдат създадени и атрибути, дефинирани от самия програмист.
Атрибутът по същество е клас, който наследява своите свойства от класа System.Attribute. По правило, класовете атрибути завършват с Attribute в техните имена, но това не се изисква при тяхното използване.
Показване на използвания атрибут чрез използването на допълнителни constructor параметри.
C# поддържа „пред-процесорни инструкции“ (макар че той не разполага с действителен пред-процесор), базирани на пред-процесора на C, което позволява на програмистите да определят символи, но не и макроси. Включени са и условни изрази като #if, #endif, и #else.
Инструкции от типа на #region дават насоки на редакторите на кода за т.нар. „свиване на кода“ (code folding). Блокът, отворен с #region трябва да бъде затворен с #endregion.
C# използва двойна наклонена черта (//) за да покаже, че останалата част от реда е коментар.
Многоредовите коментари могат да бъдат означени с отварящи наклонена черта и звездичка (/*) и затварящи звездичка и наклонена черта (*/).
Коментарите не се вграждат един в друг. Това са два отделни коментара.
Коментарите на един ред, започващи с три наклонени черти се използват за XML документиране. Трябва да се има предвид, че това е конвенция, която се използва от Microsoft Visual Studio и не е част от езика C#.
Системата за документиране, използвана в C#, е подобна на Javadoc, използвана в Java, но за разлика от нея е базирана на XML. Към момента има два метода на документиране, които се поддържат от компилатора на C#.
Подобно на често срещаните в генерирания от Visual Studio код, едноредовите коментари, използвани за документиране, се представят на ред, започващ с ///.
Многоредовите коментари, използвани за документиране, са дефинирани в спецификациите на версия 1.0 на C#. Въпреки това, те не са поддържани до пускането на |.NET 1.1. Тези коментари са обозначени с отварящи наклонена черта, звездичка, звездичка (/**) и затварящи звездичка, наклонена черта (*/).
Трябва да се има предвид, че има някои строги критерии по отношение на празно пространство (space) и XML документацията, когато се използва метода наклонена черта, звездичка, звездичка (/**).
Този блок код:
създава различен XML коментар от този блок код:
Синтаксисът за коментари, използвани за документиране, и тяхното XML представяне са определени в незадължително приложение към C# стандарта на ECMA. Същия стандарт определя и правила за обработка на такива коментари, и тяхната трансформация в обикновен XML документ с точни правила за съответствие и връзка на идентификаторите в Common Language Infrastructure (CLI) към съответстващите елементи в документацията. Това позволява на всяка интегрирана среда за разработка (IDE) на C# или на друг инструмент за разработка, да намери документация за всеки символ в кода по точно определени начин.
Двете ключови думи, които се използват при асинхронно програмиране са: модификаторът async и операторът await. Метод включващ модификаторът async се нарича асинхронен метод. Тези нови характеристики улесняват асинхронното програмиране. Например, при програмиране на Windows Forms, UI нишката ще бъде блокирана докато се използва HttpWebRequest. От гледна точна на потребителя, не може да се взаимодейства с форма преди заявката да е изпълнена.
Примерът показва, че след като натиснем Test бутона, не можем да направим никакви промени във формичката, преди резултата да се появи в текстовото поле.
Използването на метода BeginGetResponse е подобно на async метода, но е по-сложно.
Spec# е система за програмиране, която представлява разширение на езика C#. Повлияна е от JML, AsmL и Eiffel. Spec# включва структури като non-null типове, предусловия, следусловия, и обектни инварианти. Spec# е разработен от Microsoft Research.
Една от най-често срещаните грешки в обектно-ориентирани програми е причинена от разликата в null референцията. За да се избегне тази грешка Spec# прави разлика между ненулеви типове и вероятните нулеви типове. Non-null типове проверяват дали променливите от null тип не са null тип. Ако са null ще бъде хвърлен Exception.
Пример:
Предусловията се проверяват преди методът да бъде изпълнен.
Следусловията се проверяват след като методът бъде изпълнен.
Spec# добавя проверени изключения като тези в Java.
Проверката на изключения са проблематични, защото когато функция от по-ниско ниво добави нов тип изключение, цялата верига от методи, използващи този метод на някое вложено по-ниско ниво, също трябва да промени състоянието си. Това нарушава принципа отворен/затворен.