Направо към съдържанието

C Sharp синтаксис

от Уикипедия, свободната енциклопедия

Статията описва синтаксиса на програмния език C#. Описаните характеристики са съвместими с .NET Framework и Mono.

В C# имената на променливи, методи, класове и други обекти се наричат идентификатори. Те се избират от разработчика.[1] Идентификаторите могат да съдържат букви, цифри ([0 – 9]) и символа долна черта (_), но не могат да започват с цифра или символ, освен ако не е ключова дума. Идентификаторите са чувствителни към големи и малки букви (FOO е различно от foo). Те не могат да се използват като запазени ключови думи, освен ако не са оградени с @. Това включва думи като int, class, public и т.н.

Идентификаторите могат да се именуват, използвайки следните конвенции: PascalCase за имената на класове, методи, и публични свойства (FirstName, LastName), и camelCase – за имената на локални променливи и параметри на методи (firstName, lastName).[1]

Ключовите думи са предефинирани запазени идентификатори, които имат специално значение за компилатора. Езикът C# има да вида ключови думи – контекстуални и запазени. Запазените ключови думи, като false или byte, могат да бъдат използвани само като ключови думи. Контекстуалните ключови думи, като where или from, могат да бъдат третирани като такива само в определени ситуации.[2]

Ключови думи
abstract as base bool break
byte case catch char checked
class const continue decimal default
delegate do double else enum
event explicit extern false finally
fixed float for foreach goto
if implicit in int interface
internal is lock long namespace
new null object operator out
override params private protected public
readonly ref return sbyte sealed
short sizeof stackalloc static string
struct switch this throw true
try typeof uint ulong unchecked
unsafe ushort using virtual void
volatile while
Контекстуални ключови думи
add and alias ascending args
async await by descending dynamic
equals file from get global
group init into join let
managed nameof nint not notnull
nuint on or orderby partial
record remove required scoped select
set unmanaged value var when
where with yield

Контекстуалните ключови думи имат специфично значение, само в ограничен програмен контекст, и могат да бъдат използвани като идентификатори извън този контекст.[3] Някои контекстуални думи, като partial или where, имат специално значение в два или повече контекста.

Употребата на @-ключови думи за идентификатори не се препоръчва, освен за специални цели. Също знакът @ може да предхожда всеки идентификатор, но това се счита за лоша практика.[4]

Литералите са константни стойности, зададени в кода на компютърната програма. Те представляват данни в техния основен вид, които могат да се присвояват на променливи или да се използват в изрази. Пример за това се явява числото 123 (целочислен литерал), текстът изучаваме C# (текстов литерал) или буквата „ы“ (символен литерал). Всеки литерал има определен тип (или се отнася към определен тип) и се обработва в съответствие с правилата за работа със стойностите от дадения тип. Символният литерал се затваря в единични кавички, а текстовият – в двойни.

Булевите литерали са true или false. Когато присвояваме стойност на променлива от тип bool, можем да използваме единствено някоя от тези две стойности или израз от булев тип (който се изчислява до true или false).

Целочисленият литерал се реализира като стойност от най-малкия тип, започвайки от тип int, достатъчен за съхранението на стойността на литерала. Целите числа могат да бъдат представяни в десетичен, шестнадесетичен и двоичен формат. При тях се използват още следните означения:

  • 0x или 0X за шестнайсетични литерали (например 0xA8F1);
  • 'l' и 'L' като окончания означават данни от тип long (например 357L);
  • 'u' и 'U' като окончания означават данни от тип uint или ulong (например 112u).
Тип Стойности
int 12, 1234, -16
uint 12U, 12u
long 12L, 12l
ulong 12UL, 12ul
hexadecimal 0xF5, 0x[0..9, A..F, a..f]+

Реалните числови литерали (например числото 12.3) се реализират във вид на стойности от типа float, double и decimal. При тях се използват още следните означения:

  • 'f' и 'F' като окончания означават данни от тип float;
  • 'd' и 'D' като окончания означават данни от тип double;
  • 'm' и 'M' като окончания означават данни от тип decimal;
  • 'e' означава експонента, например "e-5" означава цялата част да се умножи по 10-5.
Тип Стойности
float 23.5F, 23.5f; 1.72E3F, 1.72E3f, 1.72e3F, 1.72e3f
double 23.5, 23.5D, 23.5d; 1.72E3, 1.72E3D, ...
decimal 79228162514264337593543950335m, -0.0000000000000000000000000001m, ...
exponential notation 6.02e+23, 1.72E3f, 1.72E3m

Символните литерали представляват единични символи, оградени в единични кавички (например 'F' или 'я') и се реализират като стойности от тип char. Стойността на символните литерали може да бъде:

  • символ (например 'A');
  • код на символ (например '\u0065');
  • символни последователности.
Тип Стойности
char 'a', 'Z', '\u0231', '\x30', '\n'

Текстовите литерали (низови) се затварят в двойни кавички (например "Езикът C#" или "А") и се реализират като обекти на класа String от пространството от имена System (използваме идентификатора string за израза System.String).

Тип Стойности
string "Hello, world"
"C:\\Windows\\", @"C:\Windows\" [Буквален или неформатируем литерал (с префикс @) може да включва символа \n или \b]
Интерполиран текстов литерал $"Hello, {name}!" като буквален или неформатируем литерал: $@"Hello, {name}!"

Цитираните текстови литерали се използват най-често при задаване на имена на пътища във файловата система.

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

[редактиране | редактиране на кода]

Символните последователности са литерали, които представляват последователност от специални символи, които задават символ, който по някаква причина не може да се изпише директно в програмния код. Всички последователности започват с обратно наклонен черта \, която наричаме екраниращ символ (escaping character).

Тип Стойности
символ, зададен с Unicode номера си \u
символ, зададен с ASCII номера си \x
Нулев символ[a] \0
Отместване (табулация) \t
Връщане назад \b
Връщане в изходно положение \r
Следваща страница \f
Обратна наклонена черта \\
Единична кавичка \'
Двойна кавичка \"
Нов ред \n
  1. Низовете в C# не завършват с нулев символ, така че нулевите символи могат да се появяват навсякъде в низа.

Нулевите типове (nullable types) представляват нулева стойност, която означава, че променливата не сочи към никакъв обект. Те са инстанции на структурния декоратор (Wrapper) на System.Nullable върху примитивните типове.

Нулевите типове могат да представляват неопределена или липсваща стойност за променлива, която обикновено не може да бъде null. Тези типове са полезни при работа с бази данни или други структури, които имат нулева стойност по подразбиране.

int? nullableInt = null;
double? nullableDouble = 3.14;
bool? nullableBool = true;

За проверка дали нулевият тип съдържа стойност, може да се използва метода HasValue или директна проверка за null.

Разделители за цифри

[редактиране | редактиране на кода]
Това е функционалност на C# 7.0.

Разделителите за цифри разделят цифрите в числови стойности със символа за долна черта _ за по-голяма четимост и разбиране на програмния код.

int bigNumber = 1_000_000; // 1 милион
int debitCardNumber = 1234_5678_9012_3456; // Номер на банкова карта
int socialSecurityNumber = 999_99_9999; // Единен граждански номер
double bigDouble = 1_234.567_89; // Число с плаваща запетая
decimal largeDecimal = 1_000_000.1234_5678m; // Десетично число
int hexNumber = 0xAB_CD_EF; // Шестнадесетично число
int binaryNumber = 0b1010_0011_1100_0101; // Двоично число

Променливите са именувани пространства (идентификатори) в паметта, които могат да съхраняват стойности от различни типове данни. Всяка променлива има свой тип, който влияе на обема на паметта, заделяна за променливата, а също така и на това какви стойности могат да бъдат записвани в променливата и какви операции могат да се изпълняват с променливата.

Променливите имат обхват (scope), който определя къде в кода могат да бъдат достъпвани. Обхватът на променливите се ограничава от блоковете, където са декларирани.

Преди да бъде използвана променливата, тя трябва да бъде декларирана. Декларирането може да стане практически на всяко място в програмния код, но обезателно преди променливата да бъде използвана. При деклариране на променлива може веднага да ѝ бъде присвоена стойност.

int myInt; // Деклариране на неинициализирана променлива с име 'myInt' и тип 'int'

Присвояването на стойност на променлива представлява задаване на стойност, която да бъде записана в нея. Тази операция се извършва чрез оператора за присвояване =. От лявата страна на оператора се изписва име на променлива, а от дясната страна – новата ѝ стойност.

int myInt; // Деклариране на неинициализирана променлива
myInt = 35; // Инициализиране на променливата

Инициализация означава задаване на начална стойност в момента на тяхното деклариране. Всеки тип данни в C# има стойност по подразбиране (инициализация по подразбиране), която се използва, когато за дадена променлива не бъде изрично зададена стойност.

int myInt = 35; // Едновременно деклариране и инициализиране на променливата

Няколко променливи от един и същи тип могат да бъдат декларирани и инициализирани в една декларация.

int a, b; // Деклариране на няколко променливи от един и същи тип
int a = 2, b = 3; // Деклариране и инициализирани на няколко променливи от един и същи тип

Възможно е и динамично инициализиране на променливата, когато променливата се инициализира въз основа на израз, в който влизат други променливи, ако към момента на изчисление променливите имат стойности.

Това е функционалност на C# 3.0.

Декларирането на променливи с var позволява спецификатора на декларираната променлива да бъде заменен с ключовата дума var, ако действителният ѝ тип може да бъде статично определен от инициализатора. Това намалява повторенията, особено за типове с множество типови параметри.

// Деклариране на променливи с ключовата дума 'var'
var myChars = new char[] {'A', 'Ö'}; // или char[] myChars = new char[] {'A', 'Ö'};
var myNums = new List<int>();  // или List<int> myNums = new List<int>();

Константите са имплицитно статични стойности, които не могат да бъде променяни след декларирането им.

Константите трябва да бъдат декларирани в контекста като поле или локална променлива с префикса на ключовата дума const, като стойност ѝ трябва да се даде при декларирането. След това тя е заключена и не може да бъде променяна.

// Деклариране на константа от тип double
const double PI = 3.14;

Следният пример показва всички употреби на const:

// Публичен клас Foo
public class Foo {
    // Деклариране на частна константа от тип double
    private const double X = 3;

    // Публичен метод Foo
    public Foo() {
        // Деклариране на глобална константа от тип int
        const int y = 2;
    }
}

Ключовата дума readonly прави подобно нещо за полетата. Подобно на полетата, маркирани с const, те не могат да се променят веднъж щом им се зададе начална стойност. Разликата е, че може по избор да им се зададе начална стойност в конструктора. Това важи само за полета, които са само readonly и могат да бъдат, както членове на една инстанция или статични членове на класа.

Операторите с къдрави скоби { ... } се използват за обозначаване на блок код и нова област на видимост. В този програмен блок може да се намират членовете на класа или тялото на метод.

В тялото на метод скобите може да се използват за създаване на нови обхвати:

static void Main() {
    // Променлива декларирана в главния метод:
    int a;
    // Команди
    { // Начало на вътрешен обхват
        Променливи, декларирани във вътрешен обхват:
        int b;
        a = 1;
    } // Край на вътрешен обхват
    // Команди
    a = 2;
    b = 3; // Инициализацията ще бъде неуспешна, тъй като променливата 'b' е декларирана във вътрешния обхват
}

Името на променлива, декларирана във вътрешен блок, не може да съвпада с името на променлива, декларирана във външен блок.

Структура на програмата

[редактиране | редактиране на кода]

C# приложенията се състоят от класове и техните членове. Класовете и другите типове съществуват в именни пространства, но също могат да бъдат вложени в други класове.

Методът Main е входната точка за изпълнение на всяка конзолна програма. Това е мястото, където започва изпълнението на програмата. Може да съществува само един главен метод в клас и той е статичен. Методът обикновено връща void и като списък от параметри има единствен параметър от тип масив от низове.

Главният метод има специфичен синтаксис и може да приема аргументи от командния ред.

//'Main' с 'void' тип на връщане и без параметри
class Program {
    static void Main() {
        // Код на програмата
        Console.WriteLine("Hello, World!");
    }
}
//... възможно е 'Main' метода да се дефинира с параметри от тип масив от низове
class Program {
    static void Main(string[] args) {
        // Код на програмата
        // Можете да използвате аргументите от командния ред чрез масива 'args'.
        foreach (string arg in args) {
            Console.WriteLine(arg);
        }
    }
}

Ако Main() метода не е написан по някой от указаните начини, програмата ще се компилира, но няма да може да се стартира, защото не е дефинирана правилно нейната входна точка.

В главния метода е позволено връщането на стойност на цяло число, ако има такава.

class Program {
    static int Main() {
        // Кодът на програмата
        // Връщането на 'int' стойност може да се използва като код за изход на програмата.
        Console.WriteLine("Hello, World!");
        return 0; // Код за изход.
    }
}
  • Достъпност: Методът Main трябва да бъде public или private.
  • Статичност: Методът Main трябва да бъде static, защото се извиква от средата за изпълнение на C# (Common Language Runtime - CLR) без да се създава инстанция на класа.
  • Тип на връщане: Методът Main може да връща void или int. Ако връща int, връщаната стойност обикновено се интерпретира като код за изход на програмата. 0 обикновено означава успешно изпълнение, а различна от 0 стойност показва грешка.
  • Параметри: Методът Main може да приема аргументи от командния ред като масив от низове (string[] args). Тези аргументи могат да бъдат използвани за конфигурация на програмата.
Това е функционалност на C# 7.1.

Асинхронните операции могат да бъдат изчаквани директно в Main метода, като се декларира връщащ тип на Task.

public async Task<int> CalculateSumAsync(int a, int b) {
    await Task.Delay(1000); // Симулира асинхронна операция
    return a + b;
}

Всички комбинации на Task или Task<int>, със или без параметъра string[] args са поддържани.

Програми от най-високо ниво

[редактиране | редактиране на кода]
Това е функционалност на C# 9.0.

Програмите от най-високо ниво (top-level statements) са нова функционалност, въведена в C# 9.0, която позволява на разработчиците да пишат програмен код без да се налага дефиниране на класове и методи за входна точка.

using System;
Console.WriteLine("Hello World!");

Това опростява писането на малки програми и скриптове, като премахва някои от основните шаблони.

Именни пространства

[редактиране | редактиране на кода]

Именните пространства (namespaces) представляват организирани логически групи от класове, интерфейси, структури, делегати и други типове. Именните пространства са част от името на типа и се използват за групиране и/или разграничаване на посочените структури от други такива.

namespace MyApplication {
    class Program {
        static void Main(string[] args) {
            System.Console.WriteLine("Hello World!");
        }
    }
}

Ключовата дума namespace се използва за деклариране на обхват, който съдържа множество от свързани обекти. Именните пространства може да се използват за организиране на елементите на кода и да се създадат глобално уникални типове.

Именните пространства имплицитно са с публичен достъп и това не може да се променя.

System.IO.DirectoryInfo // 'DirectoryInfo' е в именното пространство System.IO

В именните пространства може да се декларира един или повече от следните типове:

  • друго namespace
  • class
  • interface
  • struct
  • enum
  • delegate

Именните пространства могат да бъдат вложени едно в друго (.), за да осигурят още по-добра организация.

namespace MyApplication {
    namespace Utilities {
        class UtilityClass {
            public void DoSomething() {
                System.Console.WriteLine("Doing something...");
            }
        }
    }
}

C# и .NET Framework предоставят множество стандартни именни пространства, които съдържат различни основни класове и функционалности. Някои от тях включват:

  • System: Съдържа фундаментални класове и базови типове, като например System.String, System.Int32 и System.Console.
  • System.Collections.Generic: Съдържа интерфейси и класове, които дефинират различни колекции от обекти, като например List<T> и Dictionary<TKey, TValue>.
  • System.Linq: Съдържа класове и интерфейси за работа със LINQ (Language Integrat-ed Query).
  • System.IO: Съдържа типове, които поддържат входно-изходни операции.
  • System.Threading: Съдържа класове и интерфейси, които позволяват многопоточност.

Декларацията using зарежда конкретно именно пространство (namespace) от референтни събрания (асемблита). Обикновено се поставя в горната част (или заглавието) на кода, но може да бъде поставена на друго място, по желание, например вътре в класовете.

using System;
using System.Collections;

Using декларацията също може да се използва за определяне на друго име за съществуващо именно пространство или тип. Това е особено удобно, когато имената са твърде дълги и трудно разбираеми.

using Net = System.Net;
using DirInfo = System.IO.DirectoryInfo;

Директивата using static позволява зареждане на статични членове на класове, така че да се използват директно, без да се споменава името на класа.

using static System.Math;

class Program {
    static void Main() {
        double result = Sqrt(16); // Вместо Math.Sqrt(16)
        Console.WriteLine(result);
    }
}

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

Категория Оператори
Аритметични +, -, *, /, %
Логически (побитови и булеви) &, |, ^, !, ~, &&, ||, true, false
За съединяване на символни низове (конкатенация) +
За инкрементиране, декрементиране ++, --
За отместване <<, >>
За сравнение (условни) ==, !=, <, >, <=, >=
За присвояване =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
За достъпване ., ?., ?[]
За индексиране []
За кастване ()
Условни (тернарен) ?:
Делегиране на конкатенация и премахване +, -
Създаване на обекти new
За работа с типове as, is, sizeof, typeof
За контрол на изключения при препълване checked, unchecked
Насочване и адрес *, ->, [], &
За обединяване/коагулиране ??
Ламбади =>

Операторите в C# са специални символи и извършват специфични преобразувания над един, два или три операнда.

Тип Операнди
Унарни (Unary) един аргумент
Бинарни (Binary) два аргумента
Тернарни (Ternary) три аргумента

Всички бинарни оператори са ляво-асоциативни, освен операторите за присвояване на стойност. Всички оператори за присвояване на стойности и условните оператори ?: и ?? са дясно-асоциативни. Унарните оператори нямат асоциативност.

Използване на оператори:

int a = 7 + 6;
Console.WriteLine(a); // 13

string firstName = "Иван";
string lastName = "Иванов";

// Между двата низа трябва да има интервал
string fullName = firstName + " " + lastName;
Console.WriteLine(fullName); // Иван Иванов

Приоритет на операторите в C#

[редактиране | редактиране на кода]

В езика C# някой оператори имат приоритет над други. Операторите с по-висок приоритет се изчисляват преди тези с по-нисък.[2]

Приоритет Оператори
Най-висок ()
++, -- (постфиксен), new, typeof
++, -- (префиксен), + (унарни), !, ~
*, /, %
+,
<<, >>
<, >, <=, >=, is, as
==, !=
&, ^, |
&&
||
?:
Най-нисък =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

Скобите имат най-висок приоритет. Те могат да се използват за промяна на приоритетите на другите оператори. При писане на по-сложни изрази или такива с повече оператори, е препоръчително слагането на скоби, за да се избегнат трудности при четене и разбиране на кода. Добра практика е използването на скоби, дори да не изглежда необходимо.

// Първо ще се изпълни делението
x + y / 100
// Скобите правят израза по разбираем
x + (y / 100)

Овърлоудване на оператори

[редактиране | редактиране на кода]

Възможно е някои оператори да бъдат овърлоудвани (претоварени) чрез овърлоуд метода.

public static Foo operator+(Foo foo, Bar bar) {
    // Резултат от метода
    return new Boo(foo.Value + bar.Value);
}
Оператори
Унарни +, -, !, ~, ++, --, true, false
Бинарни +, -, *, /, %, &, |, ^, <<, >>
За сравняване (овърлоудват се по двойки) ==, !=, <, >, <=, >=
  • Операторите за присвояване (+=, *=, etc.) са комбинация от бинарен оператор и оператор за присвояване (=) и се оценяват чрез използване на обикновени оператори, които могат да бъдат претоварени.
  • Операторите за кастване (( )) не могат да бъдат претоварени, но може да се дефинират операторите за преобразуване.
  • Операторът за индексиране на масив ([ ]) не може да бъде претоварен, но може да се дефинират нови индексатори.

Оператори за преобразуване

[редактиране | редактиране на кода]

Операторът за кастване не може да се претоварва, но е възможно да се напише метод за преобразуване с целевия клас. Методите за преобразуване могат да дефинират два вида оператори за преобразуване: имплицитни (явни) и експлицитни (неявни).

Имплицитно преобразуване

[редактиране | редактиране на кода]

Операторът за имплицитно преобразуване (implicit typecasting) преобразува един тип в друг автоматично, без изрично да се използва какъвто и да е оператор (( )). Употребата на имплицитни оператори са позволени, когато няма възможност от загуба на данни. Такова конвертиране е от тип с по-малък обхват към тип с по-голям обхват (int към long) или когато имаме няколко типа данни с различен обхват.

class Foo {
    public int Value;

    public static implicit operator Foo(int value) {
        return new Foo(value);
    }
}
// Имплицитно преобразуване
Foo foo = 2;

Възможни неявни конверсии са:

  • sbyte > short, int, long, float, double, decimal;
  • byte > short, ushort, int, uint, long, ulong, float, double, decimal;
  • short > int, long, float, double, decimal;
  • ushort > int, uint, long, ulong, float, double, decimal;
  • char > ushort, int, uint, long, ulong, float, double, decimal;
  • uint > long, ulong, float, double, decimal;
  • int > long, float, double, decimal;
  • long > float, double, decimal;
  • ulong > float, double, decimal;
  • float > double.

Експлицитно преобразуване

[редактиране | редактиране на кода]

Операторът за експлицитно преобразуване (explicit typecasting) на типове се използва винаги, когато има вероятност за загуба на данни. Подобни загуби са възможни при конверсия на реални към целочислени типове. Или при конверсия от тип с по-голям обхват към тип с по-малък (double към float или long към int).

class Foo {
    public int Value;

    public static explicit operator Foo(int value) {
        return new Foo(value);
    }
}
// Експлицитно преобразуване
Foo foo = (Foo)2;

Операторът as ще се опита да направи мълчаливо кастване към даден вид. Ако преобразуването е успешно, операторът ще върне обекта като нов тип, ако не е успешно, ще върне нулева референция.

Stream stream = File.Open(@"C:\Temp\data.dat");
FileStream fstream = stream as FileStream; // Ще върне обект

String str = stream as String; // Ще неуспешно и ще върне null

Null-коагулиращия оператор

[редактиране | редактиране на кода]
Това е функционалност на C# 2.0.

Null-коагулиращия оператор ?? се използва за определяне на стойността по подразбиране за двата nullable типа стойност и референтни типове. Операторът връща операнда от лявата страна ifNotNullValue, ако стойността не е нула. В противен случай той връща операнда от дясната страна otherwiseValue.

Следният код

return ifNotNullValue ?? otherwiseValue;

е същият като

return ifNotNullValue != null ? ifNotNullValue : otherwiseValue;

В C# 8.0. са въведени Null-коагулиращи присвоявания.

variable ??= otherwiseValue;

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

if (variable is null) variable = otherwiseValue;

Конструкции за управление

[редактиране | редактиране на кода]

С# наследява голяма част от конструкциите за контрол от C/C++, а също и добавя нови такива, като foreach.

Условни конструкции

[редактиране | редактиране на кода]

Тези конструкции определят начина на изпълнение на програмата по зададени условия.

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

Конструкция с един оператор:

if (i == 3) ...;

Конструкция с else блок (без скоби):

if (i == 2)
    ...
else
    ...

Използването на поредица от if конструкции:

if (i == 3) {
    ...
} else if (i == 2) {
    ...
} else {
    ...
}

Switch конструкцията служи като филтър за различни стойности. Всяка стойност довежда до „случай“ – case етикет. Не е позволено отпадане на случаи и затова обикновено се използва ключовата дума break за край в case етикет. Неусловно return в case блок също може да се използва за край. Може да се разгледа как goto инструкция може да се използва, за преминаване от един case етикет към друг. Много case етикети може да доведат до еднакъв код обаче. Case етикетът – по подразбиране изпълнява всички други случаи, които не се обработват от конструкцията.

switch (условие) {
    case 'A':
        statement;
        ...
        break;
    case 'B':
        statement;
        break;
    case 'C': // Една switch конструкция може да има множество case етикети
    case 'D':
        ...
        break;
    default:
        ...
        break;
}

Итерационни структури

[редактиране | редактиране на кода]

Итерационни структури са конструкции, които са многократно изпълнявани, докато е в сила дадено условие.

while цикълът се използва, когато искаме да повтаряме извършването на определена логика, докато е в сила дадено условие. Когато условието стане грешно, while цикълът прекъсва изпълнението си и програмата продължава с изпълнението на останалия код след цикъла.

while (i == true) {
    ...
}

По структура, do ... while наподобява while. В конструкцията на do-while цикъла, условието винаги се проверява след тялото му, което от своя страна гарантира, че при първото завъртане на цикъла, кодът ще се изпълни, а проверката за край на цикъл ще се прилага върху всяка следваща итерация на do-while.

do {
    ...
}
while (i == true);

for цикълът се състои от три части: деклариране, условие и обновяване на променлива. Всяка от тях може да не бъде взета предвид, тъй като не са задължителни.

for (int i = 0; i < 10; i++) {
    ...
}

Това е еквивалент на следната while конструкция.

int i = 0;
while (i < 10) {
    //...
    i++;
}

foreach цикълът произлиза от for цикъл и следва определен модел, описан в спецификацията на езика С# за обхождане на всички елементи.

foreach (int i in list) {
    ...
}

Jump конструкциите са наследени от C/C++ и са в основата на асемблираните езици. Те представляват jump инструкция – незабавно променяне хода на изпълнение на програмата.

Етикети и goto инструкция

[редактиране | редактиране на кода]

Етикетите са определени точки в кода, към които може да се промени хода на изпълнение на програмата с помощта на goto инструкция.

start:
    ...
    goto start;

goto инструкцията може да се използва в switch конструкция за прехвърляне контрола от един към друг case етикет.

switch(n) {
    case 1:
        Console.WriteLine("Case 1");
        break;
    case 2:
        Console.WriteLine("Case 2");
        goto case 1;
    case 3:
        Console.WriteLine("Case 3");
    case 4: // Compilation will fail here as cases cannot fall through in C#
        Console.WriteLine("Case 4");
        goto default; // This is the correct way to fall through to the next case
    case 5:  // Multiple labels for the same code are OK
    case 6:
    default:
        Console.WriteLine("Default");
        break;  // Even default must not reach the end point
}

break инструкцията прекратява най-близко обхващащия цикъл или switch конструкция. Управлението се предава на конструкцията, която следва след приключената, ако има такива.

int e = 10;
for (int i = 0; i < e; i++) {
    while (true) {
        break;
    }
    // Ще се прекъсне в тази точка
}

continue инструкцията прекъсва текущата итерация на текущата конструкция за управление и започва следващата итерация.

int ch;
while (0) {
    if (ch == '...')
        continue; // Прескача останалия код от while цикъла
    ...
}

while цикълът в кода по-горе чете символи с метода GetChar() и ако символът е празно пространство, ще се прескочат останалите инструкции в тялото на цикъла.

Прихващане на изключения

[редактиране | редактиране на кода]

Методът за прихващане на изключения по време на изпълнение на програмата в С#, е наследен от Java и C/C++.

Класът System.Exception е базовият клас за всички изключения. Обектът изключение съдържа цялата информация за точно определено изключение, както и обвито изключение, ако определеното изключение съдържа причина за възникването му. Програмистите могат да определят свои собствени изключения, като произлязат от класа Exception.

Изключение може да бъде прихванато по следния начин:

    throw new NotImplementedException();

try ... catch ... finally конструкция

[редактиране | редактиране на кода]

Изключенията се управляват в try ... catch блок.

try {
    // Изрази, които могат да хвърлят изключение
    ...
} catch (Exception ex) {
    // Изключенията се прихващат и обработват тук
    ...
} finally {
    // Изрази, които винаги се изпълняват след блоковете try/catch
    ...
}

Кодът в try блока се изпълнява и ако се появи изключение, изпълнението на блока се прекъсва и изключението се прихваща в catch блока. Може да има много catch блокове, като в този случай се изпълнява първия блок с дефинирана променлива за изключение, чиито тип съвпада с типа на хвърленото изключение.

Ако няма catch блок, който да съответства на типа на хвърленото изключение, изпълнението на външния блок (или метод), съдържащ try...catch конструкцията се прекъсва и изключението се предава извън съдържащия блок или метод. Може да има много catch блокове, като в този случай се изпълнява първия блок с дефинирана променлива за изключение, чиито тип съвпада с типа на хвърленото изключение. Изключението се препредава, докато не намери съвпадение в някой от текущо активните методи. Ако изключението достигне главния метод без съвпадение, тогава цялата програма се прекратява и текстово описание на изключението се изписва на стандартния изходен поток.

Кодът в finally блока се изпълнява винаги след try и catch блоковете, независимо дали е прихванато изключение. Такива блокове са полезни за осигуряване на почистващи кодове.

Или catch блок, или finally блок, или и двата трябва да следват try блок.

C# е статично типов език като C и C++. Това означава, че на всяка променлива или константа трябва да се зададе тип, когато се декларира. Типовете данни в C# са два вида: стойностни и референтни.

Стойностните типове се съхраняват в стека, съдържат директно своята стойност и се предават по стойност. С други думи, ако се декларира променлива от стойностен тип, то стойността се съхранява директно в паметта. Стойностните типове се освобождават при излизане от обхват – стойността на декларираната променлива се изтрива от стека.

Структурите са по-известни като structs. Структурите са дефинирани от потребителя стойностни типове, които се декларират със запазената дума struct. Те са много подобни на класовете, но са по-ограничени. Някои важни синтактични разлики между клас и структура са представени в раздел Разлики между класове и структури.

struct Foo {
    ...
}

Всички примитивни типове данни са структури.

Те са примитивни типове данни.

Примитивни типове
Тип данни BCL Съответствие Стойност Обхват Размер Стойност по подразбиране
sbyte System.SByte integer −128 до +127 8-bit (1-byte) 0
short System.Int16 integer −32,768 до +32,767 16-bit (2-byte) 0
int System.Int32 integer −2,147,483,648 до +2,147,483,647 32-bit (4-byte) 0
long System.Int64 integer −9,223,372,036,854,775,808 до
+9,223,372,036,854,775,807
64-bit (8-byte) 0
byte System.Byte unsigned integer 0 до 255 8-bit (1-byte) 0
ushort System.UInt16 unsigned integer 0 до 65,535 16-bit (2-byte) 0
uint System.UInt32 unsigned integer 0 до 4,294,967,295 32-bit (4-byte) 0
ulong System.UInt64 unsigned integer 0 до 18,446,744,073,709,551,615 64-bit (8-byte) 0
decimal System.Decimal signed decimal number −79,228,162,514,264,337,593,543,950,335 до
+79,228,162,514,264,337,593,543,950,335
128-bit (16-byte) 0.0
float System.Single floating point number ±1.401298E−45 до ±3.402823E+38 32-bit (4-byte) 0.0
double System.Double floating point number ±4.94065645841246E−324 до
±1.79769313486232E+308
64-bit (8-byte) 0.0
bool System.Boolean Boolean true или false 8-bit (1-byte) false
char System.Char single Unicode character '\u0000' до '\uFFFF' 16-bit (2-byte) '\u0000'

Забележка: string (System.String) не е структура, както и не е примитивен тип.

Изброените типове (enum) са наименувани константи, представящи цели числа.

enum Season {
    Winter = 0,
    Spring = 1,
    Summer = 2,
    Autumn = 3,
    Fall = Autumn // Друго име за Autumn е Fall
}

Променливите от изброен тип имат стойност по подразбиране нула. Те могат да бъдат декларирани или инициализирани към наименувана константа, определена от изброения тип.

Season season;
season = Season.Spring;

Променливите от изброен тип имат целочислени стойности. Събиране и изваждане между променливи от един и същ тип е разрешено без конкретно преобразуване, но операциите умножение и деление са малко по-рисковани и изискват изрично (явно) преобразуване. Преобразуване се изисква също и при превръщане на променливи от изброен тип към и от целочислени типове. Въпреки това преобразуването няма да хвърли изключение, ако стойността не е зададена от дефиницията на изброения тип.

season = (Season)2; // Преобразуване на 2 в стойност на изброен тип – Season
season = season + 1; // Добавяне на 1 към стойността
season = season + season2; // Добавяне на стойностите на две променливи от изброен тип
int value = (int)season; // преобразуване на стойност от изброен тип в целочислена стойност

season++; // Season.Spring (1) става Season.Summer (2)
season--; // Season.Summer (2) става Season.Spring (1)

Стойностите могат да бъдат съединени с помощта на побитовия оператор OR

Color myColors = Color.Green|Color.Yellow|Color.Blue;

Променливи референтен тип имат строго типизиран указател (адрес на клетка от паметта). Когато конструкторът е извикан, се създава обект в динамичната памет и указател към променливата, т.е. референтните типове съдържат в стека указател към динамичната памет, където се съхранява тяхната стойност. Когато променливата на обект излиза от обхват указателят се разкача от стойността си, и когато няма указател, свързан към обекта, то той остава свободен и се маркира като боклук. По някое време системата за почистване на паметта ще изтрие тези обекти.

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

Типът масив е референтен тип, който се отнася за група от един или повече елементи от един и същ тип. Всички типове масиви произхождат от общ базов клас System.Array. Всеки елемент е посочен от своя индекс, както е при C++ и Java. Индексът представлява цяло число. Размерността на масива се определя от броя на индексите, които трябва да бъдат указани за идентифициране на елемент от масива. Под размер на масива за дадена размерност се има предвид броят на стойностите, които може да приеме индексът, съответстващ на дадената размерност. Индексацията винаги започва от 0. Размерността на даден масив може да бъде определена с помощта на свойството Rank.

Масив в C# е това, което се нарича динамичен масив при C++.

int[] numbers = new int[2];
numbers[0] = 2;
numbers[1] = 5;
int x = numbers[0];

Масивите имат удобен синтаксис за инициализация на масиви.

// Пълен синтаксис
int[] numbers = new int[5]{ 20, 1, 42, 15, 34 };
// Кратък синтаксис
int[] numbers2 = { 20, 1, 42, 15, 34 };
// Изведен синтаксис
var numbers3 = new[] { 20, 1, 42, 15, 34 };

Масивите могат да имат различни размерности, например двумерните масиви представляват матрица.

int[,] numbers = new int[3, 3];
numbers[1,2] = 2;

int[,] numbers2 = new int[3, 3] { {2, 3, 2}, {1, 2, 6}, {2, 4, 5} };
Вижте също

Класовете са самостоятелно описващи се, дефинирани от потребителя референтни типове. По същество всички типове в .NET Framework са класове, включително структури и изброени типове, които са генерирани от компилатора класове. Елементите на класа са private по подразбиране, но могат да бъдат декларирани като public, за да се виждат извън класа или protected, за да се виждат, от който и да е от наследниците на класа.

Класът System.String или накратко string е неизменима последователност от символи char), представени в Unicode таблицата.

Действия, извършени върху променлива от тип string, винаги ще връща нова такава.

string text = "Hello World!";
string substr = text.Substring(0, 5);
string[] parts = text.Split(new char[]{ ' ' });

Класът System.StringBuilder може да се използва, когато се налага променяне на символни низове.

var sb = new StringBuilder();
sb.Append('H');
sb.Append("el");
sb.AppendLine("lo!");

Интерфейсът е структура от данни, която съдържа дефиниция на роля (на група абстрактни действия) без действително осъществяване. Променлива на интерфейсен тип е указател към инстанция на клас, който имплементира този интерфейс. Вижте #Интерфейси.

C# предоставя типово-безопасни, обектно-ориентирани указатели към функции под формата на делегати.

class Program {
    // Типове делегати
    delegate int Operation(int a, int b);

    static int Add(int i1, int i2) {
        return i1 + i2;
    }

    static int Sub(int i1, int i2) {
        return i1  i2;
    }

    static void Main() {
        // Инстанциране на делегат и назначаване на метод към него
        Operation op = Add;

        // Извикване на посочения от делегата метод
        int result1 = op(2, 3);  // 5

        op = Sub;
        int result2 = op(10, 2); // 8
    }
}

Инициализиране на делегат като анонимен метод.

addition = delegate(int a, int b){ return a + b; };

Инициализиране на делегат като ламбда израз.

addition = (a, b) => a + b;

Събитията са указатели, които могат да сочат към многобройни методи. По-точно те обединяват тези указатели към един идентификатор. Това може да се разглежда като разширение на делегатите. Събитията в C# са специални инстанции на делегати, декларирани с ключовата дума event. Те обикновено се използват като тригери в UI разработването. Формата, използвана в C# и други езици от Common Language Infrastructure, се основава на това в класическия Visual Basic.

delegate void MouseEventHandler(object sender, MouseEventArgs e);

public class Button : System.Windows.Controls.Control {
    private event MouseEventHandler _onClick;

    /* Имагинерна тригерна функция */
    void Click() {
        _onClick(this, new MouseEventArgs(data));
    }
}

Събитието обикновено е придружено от метод, който ще манипулира събитие. Този метод представлява специален делегат, който има 2 параметъра – изпращач и аргументи на събитието. Типът на аргумент-обекта на събитието произхожда от класа EventArgs, който е част от базовата библиотека на CLI.

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

public class MainWindow : System.Windows.Controls.Window {
    private Button _button1;

    public MainWindow() {
        _button1 = new Button();
        _button1.Text = "Click me!";

        /* Абониране за събитие */
        _button1.ClickEvent += Button1_OnClick;

        /* Алтернативен синтаксис, който се приема за остарял:
        _button1.MouseClick += new MouseEventHandler(Button1_OnClick); */
    }

    protected void Button1_OnClick(object sender, MouseEventArgs e) {
        MessageBox.Show("Clicked!");
    }
}

Възможна е и реализация на къстъмизирано събитие:

private EventHandler _clickHandles = (s, e) => { };

	public event EventHandler Click {
		add {
			// Някакъв код за изпълнение, когато се добави манипулатор...
			...

			_clickHandles += value;
		}
		remove {
			// Някакъв код за изпълнение, когато се премахне манипулатор...
			...

			_clickHandles -= value;
		}
	}
Вижте също
Това функционалност на C# 2.0.

Нулевите типове са представени в C# 2.0. първоначално, за да се позволи стойностните типове да приемат стойност null, когато се работи с база данни.

int? n = 2;
n = null;

Console.WriteLine(n.HasValue);

Всъщност това е същото като използването на Nullable<T> структура.

Nullable<int> n = 2;
n = null;

Console.WriteLine(n.HasValue);

C# има и позволява указатели към стойностни типове (примитивни, enums и structs) в не безопасен контекст: методи и блок с код, маркирани с unsafe. Синтактично са същите като указателите в C и C++. Прекъсване по време на компилиране е изключено в не безопасен контекст.

static void Main(string[] args) {
    unsafe {
        int a = 2;
        int* b = &a;

        Console.WriteLine("Address of a: {0}. Value: {1}", (int)&a, a);
        Console.WriteLine("Address of b: {0}. Value: {1}. Value of *b: {2}", (int)&b, (int)b, *b);

        // На конзолата ще се изведе:
        // Адреса на a: 71953600. Стойност: 2
        // Адреса на b: 71953596. Стойност: 71953600. Стойност на *b: 2
    }
}

При използването на структури се изискват само структури без елементи от референтен тип като символен низ или друг клас.

public struct MyStruct {
    public char Character;
    public int Integer;
}

public struct MyContainerStruct {
    public byte Byte;
    public MyStruct MyStruct;
}

Начин на използване:

MyContainerStruct x;
MyContainerStruct* ptr = &x;

byte value = ptr->Byte;
Това е функционалност на C# 4.0 и .NET Framework 4.0.

Типът dynamic позволява динамични справки за С# по време на компилиране. Dynamic е статичен тип, който съществува само по време на компилиране.

dynamic x = new Foo();
x.DoSomething(); // Ще се компилира и анализира по време на изпълнение. Ще хвърли изключение, ако е невалиден
Това е функционалност на C# 3.0.

Анонимните типове са безименни класове, които се генерират от компилатора. Използват се за еднократна употреба. Те са много полезни в случаи, когато при LINQ заявка, която връща резултат под формата на обект с select трябва да върнат специални стойности. След това може да се определи анонимен тип, съдържащ автоматично генерирани, само за четене полета за тези стойности.

При инстанциране на друга декларация от анонимен тип със същата сигнатура, типът се подразбира от компилатора.

var carl = new { Name = "Carl", Age = 35 }; // Името на типа е известно само на компилатора
var mary = new { Name = "Mary", Age = 22 }; // Същият тип като горния израз

Опаковане и Разопаковане

[редактиране | редактиране на кода]

Опаковането е операция за преобразуване на променлива от стойностен тип в обектен тип, който представлява референтен тип.[5] Опаковането в C# е неявно преобразуване.

Разопаковането е операция за преобразуване на променлива от референтен тип (преди това трябва да е изпълнена операция Опаковане) в стойностен тип.[5] Разопаковането в C# изисква изрично (явно) преобразуване.

int foo = 42; // Променлива от стойностен тип
object bar = foo; // Променливата foo е опакована
int foo2 = (int)bar; // Рразопаковане обратно в стойностен тип

Обектно-ориентирано програмиране (ООП)

[редактиране | редактиране на кода]

C# има директна поддръжка за обектно-ориентирано програмиране.

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

В C# обектите са или указатели, или стойности. Не се обособяват други синтактични особености между тях в кода.

Всички типове, дори и стойностните типове в опакована форма, неявно наследяват System.Object класа, който е основният базов клас на всички обекти. Класът съдържа най-общите методи, споделяни от всички обекти. Някои от тях са виртуални и могат да бъдат отменени.

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

Методи

Основни методи на Object класа са:

  • Equals – поддържа сравняване между обекти.
  • Finalize – извършва операции по почистване преди обект да бъде автоматично използван отново. (Деструктор по подразбиране)
  • GetHashCode – връща число, съответстващо на стойността на обекта, относно използването на хеш таблица.
  • GetType – връща типът на текущата инстанция.
  • ToString – връща символен низ, който представлява текущия обект.

Класовете са основите на един обекто-ориентиран език, какъвто е C#. Те служат като шаблон, чрез който се описват обекти. Съдържат компоненти, които съхраняват и манипулират данни по реалистичен начин.

Разлики между класове и структури

[редактиране | редактиране на кода]

Въпреки че класовете и структурите са подобни, както по начина на деклариране, така и по това как се използват, има някои съществени разлики. Класовете са референтен тип, а структурите – стойностен тип. Структурата се съхранява в стека, когато се декларира и съдържа директно стойността си. Класовете са различни, защото съдържат в стека указател към динамичната памет, където се съхранява стойността. Указателят има тип и може да съдържа само обекти от своя тип.

Структурите изискват малко повече писане, отколкото класовете. Например трябва изрично да се създаде конструктор по подразбиране, който не изисква аргументи, за да се инициализира структурата и нейните елементи. Компилаторът ще създаде такъв по подразбиране за клас. Всички полета и свойства на една структура трябва да бъдат инициализирани преди да се създаде инстанция. Структурите не притежават финализатори и не могат да наследяват от други класове, както е при класовете. Въпреки това, те наследяват от System.ValueType, който на своя страна наследява System.Object. Структурите са по-подходящи за малки конструкции от данни.

Това е кратко обобщение на разликите:

Конструктор по подразбиране Финализатор Инициализиране на елементи Наследяване
Classes не се изисква (автоматично се генерират)[a] да не се изисква да (ако базовият клас не е sealed)
Structs изисква се (автоматично се генерират)[b] не изисква се не се поддържа
  1. Generated only if no other constructor was provided
  2. Always auto-generated, and cannot be written by the programmer

Клас се декларира по следния начин:

class Foo {
    // Деклариране елементите на класа
}
Това е функционалност на C# 2.0.

Частичен клас е деклариране на клас, чиито код е разделен в отделни файлове. Различните части на частичния клас трябва да бъдат дефинирани с ключова дума partial.

// File1.cs
partial class Foo {
    ...
}

// File2.cs
partial class Foo {
    ...
}

Преди да може да се използват елементите на класа, трябва да се инициализират променливите с указател към обект. За да се създаде обект, трябва да се извика съответния конструктор, използвайки ключова дума new. Конструкторът има същото име, както на класа.

Foo foo = new Foo();

За структурите не е задължително да се извиква конструктор, защото той се извиква автоматично по подразбиране. Трябва само да се декларира и констукторът се инициализира със стандартни стойности.

Обектни инициализатори
[редактиране | редактиране на кода]
Това е функционалност на C# 3.0.

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

var person = new Person {
    Name = "John Doe",
    Age = 39
};

// Еднозначно с:
var person = new Person();
person.Name = "John Doe";
person.Age = 39;
Инициализатори на колекции
[редактиране | редактиране на кода]
Това е функционалност на C# 3.0.

Инициализаторите на колекции имат синтаксис, подобен на този на масивите за инициализиране на колекции. Компилаторът ще генерира само извикване на Add-method. Това се отнася за класове, които имплементират ICollection интерфейс.

var list = new List<int> {2, 5, 6, 6};

// Еднозначно с:
var list = new List<int>();
list.Add(2);
list.Add(5);
list.Add(6);
list.Add(6);

Елементите на дадена инстанция и статичните компоненти на класа са достъпни чрез използването на . оператор.

Достъп до елемент на инстанция

Елементите на дадена инстанция са достъпни чрез името на променлива.

string foo = "Hello";
string fooUpper = foo.ToUpper();
Достъп до статичен компонент на клас

Статичните компоненти са достъпни чрез използването на името на класа или друг тип.

int r = String.Compare(foo, fooUpper);
Достъп до компонент чрез указател

В не безопасен контекст, членовете на стойността (структурен тип) указани чрез указател, са достъпни чрез -> оператор, точно както при C и C++.

POINT p;
p.X = 2;
p.Y = 6;
POINT* ptr = &p;
ptr->Y = 4;

Модификаторите са ключови думи, които се използват за изменение в декларирането за типове. Особено забележимо е наличието на подгрупа, съдържаща модификатори за достъп.

Модификатори за класове
  • abstract – определя, че клас служи само като базов клас. Трябва да бъде имплементиран в наследяващ клас.
  • sealed – определя, че клас не може да бъде наследен.
Модификатори за компоненти на клас
  • const – определя, че променлива има непроменлива стойност, която трябва да бъде инициализирана, когато се декларира.
  • event – декларира събитие.
  • extern – определя, че сигнатурата на метод без тяло използва DLL-import.
  • override – определя, че метод или служи за пренаписване на виртуален компонент или имплементиране на компонент от абстрактен клас.
  • readonly – декларира поле, на което могат само да бъдат зададени стойности като част от декларирането или в конструктор в същия клас.
  • unsafe – определя не безопасен контекст, който позволява употребата на указатели.
  • virtual – определя, че метод може да бъда пренаписан от класа, от който произхожда.
  • volatile – определя поле, което може да бъде изменено от външен процес и предотвратява компилатора да оптимизира промяна в употребата на полето.
static модификатор

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

public class Foo {
    public static void Something() {
        ...
    }
}
// Извикване метод на клас
Foo.Something();
Модификатори за достъп

Модификаторите за достъп, или модификатори при наследяване, определят достъпността на класовете, методите и другите компоненти. Компонент, дефиниран с public може да бъде достъпен от всякъде. Private компонентите могат да бъдат достъпни само от класа, в който са декларирани и ще бъдат скрити при наследяване. Компоненти с модификатор за достъп protected ще бъдат невидими, но достъпни при наследяване. Класове и членове с модификатор на достъп internal ще са достъпни само от ползватели на едно и също асембли.

Класовете и структурите са изрично internal, а компонентите – private, ако нямат зададен модификатор за достъп.

public class Foo {
    public int Do() {
        return 0;
    }

    public class Bar {
        ...
    }
}

Следната таблица показва къде могат да бъдат използвани модификаторите за достъп.

Невложени типове Компоненти (вкл. вложени типове) Достъпен от
public да да всякъде
protected internal не да класа, в който е деклариран, наследяващ клас и ползватели на едно и също асембли
protected не да класа, в който е деклариран и наследяващ клас
internal да (по подразбиране) да ползватели на едно и също асембли
private protected не да класа, в който е деклариран и наследяващ клас на едно и също асембли
private не да (по подразбиране) класа, в който е деклариран

Конструкторът е специален метод, който се извиква автоматично, когато се създава обект. Неговата цел е да инициализира елементите на обекта. Конструкторите имат същото име като на класа и не връщат нищо. Могат да приемат параметри като всеки друг метод.

class Foo {
    Foo() {
        ...
    }
}

Конструкторите могат да бъдат public, private или internal.

Деструкторът се вика, когато обектът ще се събира от системата за почисване на паметта, за да се извърши ръчно почистване. Има метод деструктор по подразбиране наречен finalize (финализатор), който може да се пренапише чрез деклариране на самия себе си.

Синтаксисът е подобен на този на конструкторите. Разликата е в това, че името се предхожда от ~ и не може да съдържа никакви параметри. Не може да има повече от един деструктор.

class Foo {
    ...

    ~Foo() {
        ...
    }
}

Финализаторите са винаги private.

Както в C и C++, има функции, които групират многократно използван код. Основната разлика е, че тези функци, точно както е при Java, трябва да пребивават в рамките на класа. По тази причина функцията се нарича метод. Методът има връщана стойност, име и обикновено някакви параметри, които се инициализират, когато методът се извиква с аргументи. Може или да принадлежи на инстанция на клас или да бъде статична компонента.

class Foo {
    int Bar(int a, int b) {
        return a%b;
    }
}

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

Foo foo = new Foo();
int r = foo.Bar(7, 2)

Console.WriteLine(r);

Единствената разлика между вложените методи и обикновените методи е, че вложените методи не могат да бъдат static.

Аргументи могат да се предават по референция, когато при извикване на метод към описанието на параметър в дефиницията на метода се добави ключова дума ref или out. Удобно е да се използват, когато се налага в даден метод стойността на променлива да се променя по референция, т.е. тази промяна директно се отразява на променливата. Разликата между тези два параметъра е, че преди инициализацията при out достъпът е само за писане, докато при ref достъпът е за четене и писане.

void PassRef(ref int x) {
    if (x == 2)
        x = 10;
}
int Z = 7;
PassRef(ref Z);

void PassOut(out int x) {
    x = 2;
}
int Q;
PassOut(out Q);
Незадължителни параметри
Това е функционалност на C# 4.0.

C# 4.0 въвежда незадължителни параметри със стойности по подразбиране, както е в C++.

void Increment(ref int x, int dx = 1) {
  x += dx;
}

int x = 0;
Increment(ref x); // dx приема стойност по подразбиране 1
Increment(ref x, 2); // dx приема стойност 2

Незадължителните параметри позволяват пропускането на параметри при извикване на метод. Освен това е възможно подаване на стойност на параметър чрез името му (именуван параметър) при извикване на метода, което позволява селективно преминаване през всяко подмножество от незадължителни параметри. Единственото ограничение е, че именуваните параметри трябва да бъдат поставени след неименуваните параметри. Именуваните параметри могат да определят незадължителните и задължителните параметри, както и да бъдат използвани за произволна наредба на аргументи при извикване на метода.

Stream OpenFile(string name, FileMode mode = FileMode.Open,
FileAccess access = FileAccess.Read) { ... }

OpenFile("file.txt"); // използва стойности по подразбиране "mode" и "access"
OpenFile("file.txt", mode: FileMode.Create); // използва стойности по подразбиране "access"
OpenFile("file.txt", access: FileAccess.Read); // използва стойности по подразбиране "mode"
OpenFile(name: "file.txt", access: FileAccess.Read, mode: FileMode.Create);
// именувай всички параметри за по-добра четимост,
// и използвай ред, различен от декларация на метод

Незадължителните параметри правят по-лесна съвместната работа с COM. По-рано, трябваше да се премине през всеки параметър в метода на COM компонента, дори и през тези, които не са задължителни.

object fileName = "Test.docx";
object missing = System.Reflection.Missing.Value;

doc.SaveAs(ref fileName,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing);
Console.WriteLine("Файлът е запаметен успешно");

С незадължителните параметри, кодът може да се съкрати така:

doc.SaveAs(ref fileName);

Опция на C#, е способността да се извиква собствен код. Сигнатурата на метода е проста, методът се декларира без тяло и е дефиниран с extern. Атрибутът DllImport също трябва да се добави към указателя на дадения DLL файл.

[DllImport("win32.dll")]
static extern double Pow(double a, double b);

Полетата са променливи, които се декларират в класа и съхраняват данни

class Foo {
    double foo;
}

Полетата могат да бъдат инициализирани директно, когато се декларират (освен, когато се декларират в структура).

class Foo {
    double foo = 2.3;
}
Модификатори за полета
  • const – прави полето непроменливо.
  • private – прави полето частно (по подразбиране).
  • protected – прави полето защитено.
  • public – прави полето публично.
  • readonly – разрешава на полето да бъде инициализирано само веднъж в конструктора.
  • static – прави полето статична компонента.

Свойствата имат синтаксис като този на полетата и възможностите на методите. Свойството може да има два метода за достъп: get и set.

public class Person
{
    private string _name;

    string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

// Използване на свойство
var person = new Person();
person.Name = "Robert";
Модификатори за свойства
  • private – прави свойството частно (по подразбиране).
  • protected – прави свойството защитено.
  • public – прави свойството публично.
  • static – прави свойството статичен компонент.
Модификатори за методи за достъп до свойство
  • private – прави метода за достъп частен.
  • protected – прави метода за достъп защитен.
  • public – прави метода за достъп публичен.

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

Автоматични свойства
[редактиране | редактиране на кода]
Това е функционалност на C# 3.0.

Тази опция на C# 3.0 включва автоматично имплементирани свойства. Дефинират се методи за достъп до свойство без тяло и компилаторът ще генерира полета и необходимия код за тези методи.

public double Width { get; private set; }

Индексаторите добавят възможностите на индексирането при масиви на обектите. Те се имплементират по начин подобен на свойствата.

internal class IntList
{
    private int[] _items;

    int this[int index]
    {
        get { return _items[index]; }
        set { _items[index] = value; }
    }
}

// Използване на индексатор
var list = new IntList();
list[2] = 2;

Класовете в C# могат да наследяват само от един клас. Класът може да произхожда от кой да е клас, който не е дефиниран с sealed.

class A {
    ...
}

class B : A {
    ...
}

Методи, които са дефинирани с virtual се изпълняват, но те могат да бъдат пренаписани от наследниците с ключовата дума override.

Имплементацията се определя по действителния тип на обекта, а не по типа на променливата.

class Operation {
    public virtual int Do() {
        return 0;
    }
}

class NewOperation : Operation {
    public override int Do() {
        return 1;
    }
}

При вариант на невиртуален метод с друга сигнатура, може да се използва ключова дума new. Методът, който ще се използва, ще се определи по тип на променливата, вместо по действителния тип на обекта.

class Operation {
    public int Do() {
        return 0;
    }
}

class NewOperation : Operation {
    public new double Do() {
        return 4.0;
    }
}

Това показва примера:

var operation = new NewOperation();

// Will call "double Do()" in NewOperation
double d = operation.Do();

Operation operation_ = operation;

// Ще извика "int Do()" в Operation
int i = operation_.Do();

Абстрактните класове са класове, които служат само като шаблони и няма да може да се инициализира обект от този тип. В противен случай са точно като обикновен клас.

Може да има също абстрактни компоненти. Абстрактните компоненти са компоненти на абстрактни класове, които нямат никаква имплементация. Те трябва да бъдат пренаписани от класа, който наследява компонента.

abstract class Mammal {
    public abstract void Walk();
}

class Human : Mammal {
    public override void Walk() {
        ...
    }
    ...
}

Модификаторът sealed може да се комбинира с други като незадължителен модификатор за класове, за да ги направи ненаследими.

internal sealed class Foo {
    //...
}

public class Bar {
    public virtual void Action() {
        //...
    }
}

public class Baz : Bar {
    public sealed override void Action() {
        //...
    }
}

Интерфейсът (interface) е абстрактен тип, който съдържа само декларации на методи, свойства, събития или индексатори, но не и техните имплементации. Класовете или структурите, които имплементират интерфейс, трябва да предоставят имплементация за всички членове на интерфейса. Те са полезни, когато трябва да се определи договор между компоненти с различни типове, които имат различни имплементации. Интерфейсните компоненти са public. Интерфейсът може да бъде имплементиран неявно (explicit) или явно (implicit).

interface IBinaryOperation {
    double A { get; set; }
    double B { get; set; }

    double GetResult();
}

Имплементиране на интерфейс

[редактиране | редактиране на кода]

Интерфейсът се имплементира от клас или като разширение към друг интерфейс по същия начин, по който произлиза клас от друг клас с помощта на : нотация.

Неявна имплементация
[редактиране | редактиране на кода]

Когато интерфейс се имплементира неявно (explicit implementation), неговите компоненти трябва да бъдат public.

public class Adder : IBinaryOperation {
    public double A { get; set; }
    public double B { get; set; }

    public double GetResult() {
        return A + B;
    }
}

public class Multiplier : IBinaryOperation {
    public double A { get; set; }
    public double B { get; set; }

    public double GetResult() {
        return A*B;
    }
}

Начин на използване:

IBinaryOperation op = null;
double result;

// Adder имплементира интерфейса IBinaryOperation.

op = new Adder();
op.A = 2;
op.B = 3;

result = op.GetResult(); // 5

// Multiplier също имплементира този интерфейс
op = new Multiplier();
op.A = 5;
op.B = 4;

result = op.GetResult(); // 20
Явна имплементация
[редактиране | редактиране на кода]

При явна имплементация (implicit implementation) могат да се имплементират едновременно няколко интерфейса, които съдържат методи с еднаква сигнатура. Може също така компонентите да се имплементират явно, които са достъпни само, когато обектът се използва като интерфейс.

public class Adder : IBinaryOperation {
    double IBinaryOperation.A { get; set; }
    double IBinaryOperation.B { get; set; }

    double IBinaryOperation.GetResult() {
        return ((IBinaryOperation)this).A + ((IBinaryOperation)this).B;
    }
}

Начин на използване:

Adder add = new Adder();

// Тези компоненти не са достъпни
// add.A = 2;
// add.B = 3;
// double result = add.GetResult();

// Преобразуване на типа на интерфейса, за да има достъп до тях
IBinaryOperation add2 = add;
add2.A = 2;
add2.B = 3;

double result = add2.GetResult();

Свойствата в класа, който обхваща IBinaryOperation са автоматично имплементирани от компилатора и полето е автоматично добавено (виж #Автоматични свойства).

Разширяване от множество интерфейси
[редактиране | редактиране на кода]

Интерфейсите и класовете позволяват да се разширяват от множество интерфейси.

class MyClass : IInterfaceA, IInterfaceB {
    ...
}

Това е интерфейс, който е разширен от два интерфейса.

interface IInterfaceC : IInterfaceA, IInterfaceB {
    ...
}

Интерфейси и абстрактни класове

[редактиране | редактиране на кода]

Интерфейсите и абстрактните класове са подобни. Ето по какво се различават:

  • Абстрактният клас може да има член-променливи, както и неабстрактни методи или свойства. Интерфейсът не може.
  • Класът или абстрактният клас може да наследява един клас или абстрактен клас.
  • Класът или абстрактният клас може да имплементира един или повече интерфейси.
  • Интерфейсът може само да разшири други интерфейси.
  • Абстрактният клас може да има непублични методи и свойства (също и абстрактни такива). Интерфейсът може да има само публични компоненти.
  • Абстрактният клас може да има константи, статични методи и статични компоненти. Интерфейсът не може.
  • Абстрактният клас може да има конструктори. Интерфейсът не може.
Това е функционалност на C# 2.0 и .NET Framework 4.0.

Шаблонните типове (или параметризирани типове) са мощна характеристика, която позволява създаването на методи и класове, в които не се конкретизира типа данни до момента на инстанциране на класа или метода.

Декларирането на шаблонен тип от класа Generic става със задаване на типа данни в счупените скоби (<...>).

List<T> someList= new List<T>;

Където Т е типа данни.

Колекцията List<T> е динамичен масив, който няма фиксиран размер и позволява директен достъп по индекс на елементите му.

Деклариране
class SomeClass {
    static void Main() {
        // Създаване на списък от цели числа
		List<int> list = new List<int>();
        list.Add(8); // Добавяне на елемент
        list.Remove(8); // Премахване на елемент
        list.Sort(); // Сортиране на елемент
	}
}

List<T> се реализира чрез използването на параметризирани типове, което означава, че типът на елементите се определя, когато се създава списъкът.

Методи

Основни методи на класа List<T>:

  • Add(T item): Добавя елемент в края на списъка.
  • AddRange(IEnumerable<T> collection): Добавя елементите на колекцията в края на списъка.
  • Clear(): Изчиства всички елементи от списъка.
  • Contains(T item): Проверява дали даден елемент съществува в списъка.
  • CopyTo(T[] array, int arrayIndex): Копира елементите на списъка в масив, започвайки от даден индекс на масива.
  • Find(Predicate<T> match): Намира първия елемент, който отговаря на условието, зададено от предикат.
  • FindAll(Predicate<T> match): Намира всички елементи, които отговарят на условието, зададено от предикат.
  • FindIndex(Predicate<T> match): Намира индекса на първия елемент, който отговаря на условието, зададено от предикат.
  • FindLast(Predicate<T> match): Намира последния елемент, който отговаря на условието, зададено от предикат.
  • FindLastIndex(Predicate<T> match): Намира индекса на последния елемент, който отговаря на условието, зададено от предикат.
  • ForEach(Action<T> action): Изпълнява действие върху всеки елемент в списъка.
  • IndexOf(T item): Намира индекса на първото срещане на даден елемент в списъка.
  • Insert(int index, T item): Вмъква елемент на определена позиция в списъка.
  • InsertRange(int index, IEnumerable<T> collection): Вмъква елементите на колекцията, започвайки от даден индекс в списъка.
  • Remove(T item): Премахва първото срещане на даден елемент от списъка.
  • RemoveAll(Predicate<T> match): Премахва всички елементи, които отговарят на условието, зададено от предикат.
  • RemoveAt(int index): Премахва елемента на определена позиция в списъка.
  • RemoveRange(int index, int count): Премахва определен брой елементи, започвайки от даден индекс.
  • Reverse(): Обръща реда на елементите в списъка.
  • Sort(): Сортира елементите в списъка по подразбиране.
  • Sort(Comparison<T> comparison): Сортира елементите в списъка, използвайки определена функция за сравнение.
  • ToArray(): Преобразува списъка в масив.
  • TrimExcess(): Намалява капацитета на списъка до броя на елементите в него.
  • TrueForAll(Predicate<T> match): Проверява дали всички елементи в списъка отговарят на условието, зададено от предикат.

Свързаните списъци (LinkedList<T>) представляват колекция от списъци свързани по между си. Те ни дават възможност да поставяме елементи на желани от нас позиции.

Деклариране
class SomeClass {
    static void Main() {
        // Създаване на списък от цели числа
        LinkedList<int> list = new LinkedList<int>();
        list.AddFirst(8); // Добавяне на нов възел в началото
        list.AddLast(12); // Добавяне на нов възел в началото
        list.AddAfter(linkedList.First, 5); // Добавяне на елемент след даден възел
        list.addBefore(linkedList.Last, 3); // Добавяне на елемент преди даден възел
    }
}
Методи

Основни методи на класа LinkedList<T>:

  • AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode): Добавя нов възел след даден възел в свързания списък.
  • AddAfter(LinkedListNode<T> node, T value): Добавя нов елемент след даден възел в свързания списък.
  • AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode): Добавя нов възел преди даден възел в свързания списък.
  • AddBefore(LinkedListNode<T> node, T value): Добавя нов елемент преди даден възел в свързания списък.
  • AddFirst(LinkedListNode<T> node): Добавя нов възел в началото на свързания списък.
  • AddFirst(T value): Добавя нов елемент в началото на свързания списък.
  • AddLast(LinkedListNode<T> node): Добавя нов възел в края на свързания списък.
  • AddLast(T value): Добавя нов елемент в края на свързания списък.
  • Clear(): Изчиства всички възли от свързания списък.
  • Contains(T value): Проверява дали даден елемент съществува в свързания списък.
  • Find(T value): Намира първия възел, съдържащ дадената стойност.
  • FindLast(T value): Намира последния възел, съдържащ дадената стойност.
  • Remove(LinkedListNode<T> node): Премахва даден възел от свързания списък.
  • Remove(T value): Премахва първото срещане на даден елемент от свързания списък.
  • RemoveFirst(): Премахва първия възел от свързания списък.
  • RemoveLast(): Премахва последния възел от свързания списък.

Стекът (Stack<T>) представлява структура от данни от тип „последният влязъл, първи излиза“ (Last In, First Out). При структурите от данни от тип Stack<T> липсва индексатор и затова имаме достъп само до последния елемент, който е влязъл.

Деклариране
class SomeClass {
    static void Main() {
        // Създаване на списък от цели числа
        Stack<int> list = new Stack<int>();
        list.Push(8); // Добавяне на елемент
        list.Pop(); // Премахване на елемент
    }
}
Методи

Основни методи на класа Stack<T>:

  • Clear(): Изчиства всички елементи от стека.
  • Contains(T item): Проверява дали даден елемент съществува в стека.
  • CopyTo(T[] array, int arrayIndex): Копира елементите на стека в масив, започвайки от даден индекс на масива.
  • Peek(): Връща елемента в горната част на стека, без да го премахва.
  • Pop(): Премахва и връща елемента в горната част на стека.
  • Push(T item): Добавя елемент в горната част на стека.
  • ToArray(): Преобразува стека в масив.
  • TrimExcess(): Намалява капацитета на стека до броя на елементите в него.

Опашката (Queue<T>) представлява структура от данни от тип „първият влязъл, първи излиза“ (First In, First Out). При структурите от данни от тип Queue<T> липсва индексатор и затова имаме достъп само до първия елемент, който е влязъл.

Деклариране
class SomeClass {
    static void Main() {
        // Създаване на списък от цели числа
        Queue<int> list = new Queue<int>();
        list.Enqueue(8); // Добавяне на елемент
        list.Dequeue(); // Премахване на елемент
    }
}
Методи

Основни методи на класа Queue<T>:

  • Clear(): Изчиства всички елементи от опашката.
  • Contains(T item): Проверява дали даден елемент съществува в опашката.
  • CopyTo(T[] array, int arrayIndex): Копира елементите на опашката в масив, започвайки от даден индекс на масива.
  • Dequeue(): Премахва и връща елемента в началото на опашката.
  • Enqueue(T item): Добавя елемент в края на опашката.
  • Peek(): Връща елемента в началото на опашката, без да го премахва.
  • ToArray(): Преобразува опашката в масив.
  • TrimExcess(): Намалява капацитета на опашката до броя на елементите в нея.

HashSet<T> представлява колекция от уникални елементи.

Деклариране
class SomeClass {
    static void Main() {
        // Създаване на списък от цели числа
        HashSet<int> numbers = new HashSet<int> { 1, 2, 3, 4, 5 };
        numbers.Add(6); // Добавяне на елемент
        numbers.Remove(2); // Премахване на елемент
    }
}
Методи

Основни методи на класа HashSet<T>:

  • bool Add(T item): Добавя елемент към множеството и връща true, ако елементът е добавен успешно, или false, ако елементът вече съществува в множеството.
  • void Clear(): Премахва всички елементи от множеството.
  • bool Contains(T item): Проверява дали даден елемент съществува в множеството.
  • void CopyTo(T[] array): Копира елементите на множеството в масив, започвайки от първия елемент на масива.
  • bool Remove(T item): Премахва даден елемент от множеството и връща true, ако елементът е успешно премахнат, или false, ако елементът не е намерен.
  • void ExceptWith(IEnumerable<T> other): Премахва всички елементи, които се съдържат в дадена колекция от текущото множество.
  • void IntersectWith(IEnumerable<T> other): Запазва само тези елементи в текущото множество, които се съдържат и в дадената колекция.
  • bool IsProperSubsetOf(IEnumerable<T> other): Проверява дали текущото множество е строго подмножество на дадена колекция.
  • bool IsProperSupersetOf(IEnumerable<T> other): Проверява дали текущото множество е строго надмножество на дадена колекция.
  • bool IsSubsetOf(IEnumerable<T> other): Проверява дали текущото множество е подмножество на дадена колекция.
  • bool IsSupersetOf(IEnumerable<T> other): Проверява дали текущото множество е надмножество на дадена колекция.
  • bool Overlaps(IEnumerable<T> other): Проверява дали текущото множество и дадената колекция имат поне един общ елемент.
  • bool SetEquals(IEnumerable<T> other): Проверява дали текущото множество и дадената колекция съдържат едни и същи елементи.
  • void SymmetricExceptWith(IEnumerable<T> other): Модифицира текущото множество така, че да съдържа само елементи, които се съдържат или в текущото множество, или в дадената колекция, но не и в двете.
  • void UnionWith(IEnumerable<T> other): Модифицира текущото множество така, че да съдържа всички елементи, които се съдържат или в текущото множество, или в дадената колекция.
Свойства
  • int Count { get;}: Връща броя на елементите в множеството.
  • IEqualityComparer<T> Comparer { get;}: Връща обекта за сравнение, използван за определяне на равенството на елементите в множеството.

Интерфейс на класа Generic

[редактиране | редактиране на кода]

Интерфейсите на класа Generic (generic interfaces) предоставят шаблони типове, които могат да работят с различни типове данни.

В .NET Framework има няколко вградени интерфейса:

  • ICollection<T>: Този интерфейс наследява от IEnumerable<T> и предоставя методи за работа с колекция от определен тип.
    • int Count { get;}: Връща броя на елементите в колекцията.
    • bool IsReadOnly { get;}: Връща дали колекцията е само за четене.
    • void Add(T item): Добавя елемент към колекцията.
    • void Clear(): Изчиства всички елементи от колекцията.
    • bool Contains(T item): Проверява дали даден елемент съществува в колекцията.
    • void CopyTo(T[] array, int arrayIndex): Копира елементите на колекцията в масив, започвайки от даден индекс на масива.
    • bool Remove(T item): Премахва първото срещане на даден елемент от колекцията.
  • IComparer<T>: Този интерфейс предоставя метод за сравнение на два обекта от определен тип. Този интерфейс е особено полезен при сортиране на колекции.
    • Compare(T x, T y): Сравнява два обекта и връща цяло число, което показва техния относителен ред.
      • Връща отрицателна стойност, ако x е по-малко от y.
      • Връща нула, ако x е равно на y.
      • Връща положителна стойност, ако x е по-голямо от y.
  • IEnumerable<T>: Този интерфейс предоставя механизъм за итериране през колекция от определен тип.
    • IEnumerator<T> GetEnumerator(): Връща итериращ обект, който може да се използва за преминаване през колекцията.
  • IEnumerator<T>: Този интерфейс предоставя механизъм за итериране през колекция от определен тип.
    • bool MoveNext(): Премества курсора към следващия елемент в колекцията. Връща true, ако курсорът успешно е преместен към следващия елемент; в противен случай връща false.
    • void Reset(): Премества курсора в началната позиция, която е преди първия елемент в колекцията.
    • void Dispose(): Освобождава всички ресурси, използвани от текущия екземпляр на IEnumerator<T>.
    • T Current { get;}: Получава текущия елемент в колекцията.
  • IList<T>: Този интерфейс наследява от ICollection<T> и предоставя методи за работа с колекция, която има индексирани елементи.
    • T this[int index] { get; set;}: Получава или задава елемента на даден индекс.
    • int IndexOf(T item): Връща индекса на първото срещане на даден елемент в колекцията.
    • void Insert(int index, T item): Вмъква елемент на определена позиция в колекцията.
    • void RemoveAt(int index): Премахва елемента на определена позиция в колекцията.
  • ISet<T>: Този интерфейс представлява колекция от уникални елементи.
    • bool Add(T item): Добавя елемент в множеството и връща true, ако елементът е добавен успешно, или false, ако елементът вече съществува в множеството.
    • void ExceptWith(IEnumerable<T> other): Премахва всички елементи, които се съдържат в дадена колекция от текущото множество.
    • void IntersectWith(IEnumerable<T> other): Запазва само тези елементи в текущото множество, които се съдържат и в дадената колекция.
    • bool IsProperSubsetOf(IEnumerable<T> other): Проверява дали текущото множество е строго подмножество на дадена колекция.
    • bool IsProperSupersetOf(IEnumerable<T> other): Проверява дали текущото множество е строго надмножество на дадена колекция.
    • bool IsSubsetOf(IEnumerable<T> other): Проверява дали текущото множество е подмножество на дадена колекция.
    • bool IsSupersetOf(IEnumerable<T> other): Проверява дали текущото множество е надмножество на дадена колекция.
    • bool Overlaps(IEnumerable<T> other): Проверява дали текущото множество и дадената колекция имат поне един общ елемент.
    • bool SetEquals(IEnumerable<T> other): Проверява дали текущото множество и дадената колекция съдържат едни и същи елементи.
    • void SymmetricExceptWith(IEnumerable<T> other): Модифицира текущото множество така, че да съдържа само елементи, които се съдържат или в текущото множество, или в дадената колекция, но не и в двете.
    • void UnionWith(IEnumerable<T> other): Модифицира текущото множество така, че да съдържа всички елементи, които се съдържат или в текущото множество, или в дадената колекция.
  • IDictionary<TKey, TValue>: Този интерфейс наследява от ICollection<KeyValuePair<TKey, TValue>> и предоставя методи за работа с колекция от двойки ключ-стойност.
    • int Count { get;}: Връща броя на двойките ключ-стойност, съдържащи се в речника.
    • ICollection<TKey> Keys { get;}: Връща колекция от ключовете в речника.
    • ICollection<TValue> Values { get;}: Връща колекция от стойностите в речника.
    • TValue this[TKey key] { get; set;}: Получава или задава стойността, свързана с даден ключ.
    • void Add(TKey key, TValue value): Добавя елемент с даден ключ и стойност към речника.
    • bool ContainsKey(TKey key): Проверява дали речникът съдържа даден ключ.
    • bool ContainsValue(TValue value): Проверява дали речникът съдържа дадена стойност.
    • bool Remove(TKey key): Премахва елемента с даден ключ от речника.
    • bool TryGetValue(TKey key, out TValue value): Опитва се да получи стойността, свързана с даден ключ.
    • void Clear(): Премахва всички елементи от речника.

Типовете данни Т (параметризирани типове) са имена, които заменят конкретен тип данни при създаването на нов обект от класа Generic. Те могат да бъдат обвързани с класове, или методи чрез поставяне на типа Т в счупените скоби <...>. След декларацията на класа, или метода може да се извиква и подаде конкретен тип данни.

Ограничения Обяснение
Where T:struct
типовете данни трябва да бъдат целочислени
where Т:class
типовете данни трябва да бъдат референтни
where T:new()
типовете данни трябва да имат конструктор които не приема параметри
where Т:<base_class>
типът данни трябва да бъде наследник на <base_class>
where T:<interface>
типът данни трябва да имплементира този интерфейс
where T : U
голо типово ограничение

Ковариантност и контравариантност

[редактиране | редактиране на кода]
Това е функционалност на C# 4.0 и .NET Framework 4.0.

Ковариантността и контравариантността са концепции на класа Generic, които определят как типовете могат да бъдат заменяни с по-общи или по-специфични типове. Тези концепции се прилагат при интерфейси и делегати.

Например, съществуващият интерфейс IEnumerable<T> се задава, както следва:

interface IEnumerable<out T> {
    IEnumerator<T> GetEnumerator();
}

Следователно, всеки клас, който имплементира IEnumerable<Derived> за класа Derived, също се счита за съвместим с IEnumerable<Base> за всички класове и интерфейси на Base, които Derived разширява, пряко или непряко.

void PrintAll(IEnumerable<object> objects) {
    foreach (object o in objects) {
        System.Console.WriteLine(o);
    }
}

IEnumerable<string> strings = new List<string>();
PrintAll(strings); // IEnumerable<string> е явно преобразувано в IEnumerable<object>

За контравариантност, съществуващият интерфейс IComparer<T> се задава, както следва:

public interface IComparer<in T> {
    int Compare(T x, T y);
}

Следователно, всеки клас, който имплементира IComparer<Base> за класа Base, също се счита за съвместим с IComparer<Derived> за всички класове и интерфейси на Derived, които са разширени от Base.

IComparer<object> objectComparer = GetComparer();
IComparer<string> stringComparer = objectComparer;

Енумераторът представлява итератор. Енумераторите се използват чрез извикване на метода GetEnumerator() на обект, който имплементира IEnumerable интерфейс. Контейнерните класове обикновено реализират този интерфейс. Въпреки това, операторът foreach може да работи с всеки обект, предоставящ такъв метод, дори ако не имплементира IEnumerable интерфейс.

Примера показва проста употреба на итератори в С# 2.0.

// Експлицитен вариант
IEnumerator<MyType> iter = list.GetEnumerator();
while (iter.MoveNext())
    Console.WriteLine(iter.Current);

// Имплицитен вариант
foreach (MyType value in list)
    Console.WriteLine(value);

Функционалност на генератор

[редактиране | редактиране на кода]

.NET 2.0 Framework позволява на C# да представи итератор, който има функционалността на генератор чрез конструкцията yield return, подобна на yield в Pythonyield return, функцията автоматично запазва състоянието си по време на итерацията.

// Метода взима итератор като вход
public static IEnumerable<int> GetEven(IEnumerable<int> numbers) {
    foreach (int i in numbers) {
        if (i%2 == 0)
            yield return i;
    }
}

static void Main() {
    int[] numbers = { 1, 2, 3, 4, 5, 6};
    foreach (int i in GetEven(numbers))
        Console.WriteLine(i);  // Извежда 2, 4 и 6
}
Това е функционалност на C# 3.0 и .NET Framework 3.0.

LINQ, съкратено от Language Integrated Queries, представлява редица разширения на .NET Framework, които включват интегрирани в езика заявки и операции върху елементи от даден източник на данни (най-често масиви и колекции). LINQ обработва колекциите по подобие на SQL езиците, които обработват редовете в таблици в база данни. Той е част от C# и VisualBasic синтаксиса и се състои от няколко основни ключови думи.[1]

С ключовите думи from и in се задават източникът на данни (колекция, масив и т.н.) и променливата, с която ще се итерира (обхожда) по колекцията (обхождане по подобие на foreach оператора).[1]

var list = new List<int>{ 2, 7, 1, 3, 9 };

var result = from i in list
               where i > 1
               select i;

Разликата от SQL е мястото на ключовата дума from. В SQL е на последно място, а в C# е на първо. Това е така заради синтаксиса на C#.

Анонимните методи, или в настоящата си форма по-често цитирани като „ламбда изрази“, са функционалност, която позволява да се пишат функции, подобни на затварящи, директно вградени в кода.

Съществуват различни начини за създаване на анонимни методи. Преди въвеждането на C# 3.0 има ограничена поддръжка, чрез използването на делегати.

Това е функционалност на C# 2.0.

Анонимни делегати са указатели към функции, които съдържат анонимни методи. Целта е да се улесни използването на делегати чрез опростяване процеса на възлагане на функцията. Вместо да създава отделен метод, програмистът, използвайки правилния синтаксис, може да напише функцията директно в кода си, а компилаторът ще генерира анонимна функция за него.

Func<int, int> f = delegate(int x) { return x * 2; };
Това е функционалност на C# 3.0.

Ламбда изразите представляват анонимни функции, които съдържат изрази или последователност от оператори. Всички ламбда изрази използват ламбда оператора =>. Лявата страна на ламбда оператора определя входните параметри на анонимната функция, а дясната страна представлява израз или последователност от оператори, която работи с входните параметри и евентуално връща някакъв резултат.

Обикновено ламбда изразите се използват като предикати или вместо делегати (променливи от тип функция), които се прилагат върху колекции, обработвайки елементите от колекцията по някакъв начин и/или връщайки определен резултат.

// [Аргументи] => [тяло на метода]

// С въведени параметри
n => n == 2
(a, b) => a + b
(a, b) => { a++; return a + b; }

// С изрично посочени параметри
(int a, int b) => a + b

// Без въведени параметри
() => return 0

// Присвояване на ламбда израз на делегат
Func<int, int, int> f = (a, b) => a + b;

Телата на ламбда изрази, съдържащи повече от един израз, са оградени от скоби. Вътре в скобите, кода може да бъде написан като стандартен метод.

(a, b) => { a++; return a + b; }

Ламбда изразите могат да бъдат подавани и като аргументи при извикването на метод, подобно на анонимни делегати, но с по-естетичен синтаксис.

var list = stringList.Where(n => n.Length > 2);

Ламбда изразите по същество са методи, генерирани от компилатора, които се подават чрез делегати. Тези методи са запазени само за компилатора и не могат да се използват в друг контекст.

Това е функционалност на C# 3.0.

Допълващите (разширяващи) методи са форма на т.нар. „синтактична захар“. Те създават илюзията за добавяне на нови методи към съществуващ клас извън неговото дефиниране. На практика, допълващият метод е статичен метод, който може да се извика по същия начин, както и инстантния метод. Извикващия метод е обвързан с първия параметър на метода, обозначен от ключовата дума this.

public static class StringExtensions{
    public static string Left(this string s, int n) {
        return s.Substring(0, n);
    }
}

string s = "foo";
s.Left(3); // Същото като StringExtensions.Left(s, 3);
Това е функционалност на C# 7.0.

Локалните функции могат да се дефинират във всеки един друг метод, конструктор или get и set свойства. Локалните функции имат достъп до променливи, които се използват в съдържащия ги метод. Модификаторите за достъп (public, private, protected) не могат да се използват с локални функции. Локалните функции не поддържат предефиниране на функции, т.е. не може да има две локални функции в един и същи метод с едно и също име, дори и ако сигнатурите на двете функции се различават по списъка от параметри (броят на елементите в него или подредбата им). Когато компилаторът компилира такива функции, те биват превърнати в private static методи.

В примера методът Sum() е локална функция, тъй като е вложен в метода Main(), т.е. Sum() е локален за Main(). Това означава, че методът Sum() може да бъде използван само в метода Main(), тъй като е деклариран в него.

static void Main(string[] args) {
    int Sum(int x, int y) {
        return x + y;
    }
    // Извежда сбора от числата 10 и 20
    Console.WriteLine(Sum(10, 20));
}

C# изпълнява затварящи блокове чрез използването на using конструкцията. Using конструкцията приема израз, чийто резултат е обект, включващ IDisposable. Компилаторът генерира код, който гарантира затварянето (унищожаването) на обекта, когато кода, в обхвата на using конструкцията, бъде изпълнен. Използването на using конструкцията е „синтактична захар“. Тя прави кода по-четим от еквивалентния му блок try ... finally.

public void Foo() {
    using (var bar = File.Open("Foo.txt")) {
        // Прави нещо
        throw new Exception();
        // Хвърля изключение
    }
}

Синхронизация на нишки

[редактиране | редактиране на кода]

Lock конструкцията в C# е още един пример за ползите от „синтактичната захар“. Той действа чрез маркирането на блок от кода като критична зона като взаимно изключва достъпа до посочения обект. Подобно на using конструкцията, lock работи като компилатора генерира try ... finally блок на негово място.

private static StreamWriter _writer;

public void ConcurrentMethod() {
    lock (_writer) {
        _writer.WriteLine("Line 1.");
        _writer.WriteLine("Followed by line 2.");
    }
}

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

[CompilerGenerated]
public class $AnonymousType$120 {
    [CompilerGenerated]
    public string Name { get; set; }
}

.NET Framework идва с предварително зададени атрибути, които могат да бъдат използвани. Някои от тях играят важна роля по време на изпълнението на програмата, докато други са просто синтактична украса в кода (например CompilerGenerated – само маркира, че е елемент, генериран от компилатора). Също така могат да бъдат създадени и атрибути, дефинирани от самия програмист.

Атрибутът по същество е клас, който наследява своите свойства от класа System.Attribute. По правило, класовете атрибути завършват с Attribute в техните имена, но това не се изисква при тяхното използване.

public class EdibleAttribute : Attribute {
    public EdibleAttribute() : base() {

    }

    public EdibleAttribute(bool isNotPoisonous) {
        this.IsPoisonous = !isNotPoisonous;
    }

    public bool IsPoisonous { get; set; }
}

Показване на използвания атрибут чрез използването на допълнителни constructor параметри.

[Edible(true)]
public class Peach : Fruit
{
   // Членове, ако има такива
}

C# поддържа „пред-процесорни инструкции“[6] (макар че той не разполага с действителен пред-процесор), базирани на пред-процесора на C, което позволява на програмистите да определят символи, но не и макроси. Включени са и условни изрази като #if, #endif, и #else.

Инструкции от типа на #region дават насоки на редакторите на кода за т.нар. „свиване на кода“ (code folding). Блокът, отворен с #region трябва да бъде затворен с #endregion.

public class Foo {
    #region Constructors
    public Foo() {}
    public Foo(int firstParam) {}
    #endregion

    #region Methods
    public void IntBar(int firstParam) {}
    public void StrBar(string firstParam) {}
    public void BoolBar(bool firstParam) {}
    #endregion
}

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

[редактиране | редактиране на кода]

C# използва двойна наклонена черта (//) за да покаже, че останалата част от реда е коментар.

public class Foo {
    // Коментар
    public static void Bar(int firstParam) {}  // Също коментар
}

Многоредовите коментари могат да бъдат означени с отварящи наклонена черта и звездичка (/*) и затварящи звездичка и наклонена черта (*/).

public class Foo {
    /* Многоредов
       коментар  */
    public static void Bar(int firstParam) {}
}

Коментарите не се вграждат един в друг. Това са два отделни коментара.

// Може да се изпише /* */ */ */ /* /*
/* Може да се изпише /* /* /* но завършва с */

Коментарите на един ред, започващи с три наклонени черти се използват за XML документиране. Трябва да се има предвид, че това е конвенция, която се използва от Microsoft Visual Studio и не е част от езика C#.

    /// <summary>
    /// Този клас е много класен
    /// </summary>

Система за документиране в XML

[редактиране | редактиране на кода]

Системата за документиране, използвана в C#, е подобна на Javadoc, използвана в Java, но за разлика от нея е базирана на XML. Към момента има два метода на документиране, които се поддържат от компилатора на C#.

Подобно на често срещаните в генерирания от Visual Studio код, едноредовите коментари, използвани за документиране, се представят на ред, започващ с ///.

public class Foo {
    /// <summary>Обобщение на метода</summary>
    /// <param name="firstParam">Описание на параметъра</param>
    /// <remarks>Забележки към метода</remarks>
    public static void Bar(int firstParam) {}
}

Многоредовите коментари, използвани за документиране, са дефинирани в спецификациите на версия 1.0 на C#.[7] Въпреки това, те не са поддържани до пускането на |.NET 1.1. Тези коментари са обозначени с отварящи наклонена черта, звездичка, звездичка (/**) и затварящи звездичка, наклонена черта (*/).[8]

public class Foo {
    /** <summary>Обобщение на метода</summary>
     *  <param name="firstParam">Описание на параметъра</param>
     *  <remarks>Забележки към метода</remarks> */
    public static void Bar(int firstParam) {}
}

Трябва да се има предвид, че има някои строги критерии по отношение на празно пространство (space) и XML документацията, когато се използва метода наклонена черта, звездичка, звездичка (/**).

Този блок код:

/**
 * <summary>
 * Кратко описание на метода.</summary>*/

създава различен XML коментар от този блок код:[8]

/**
 * <summary>
   Кратко описание на метода. </summary>*/

Синтаксисът за коментари, използвани за документиране, и тяхното XML представяне са определени в незадължително приложение към C# стандарта на ECMA. Същия стандарт определя и правила за обработка на такива коментари, и тяхната трансформация в обикновен XML документ с точни правила за съответствие и връзка на идентификаторите в Common Language Infrastructure (CLI) към съответстващите елементи в документацията. Това позволява на всяка интегрирана среда за разработка (IDE) на C# или на друг инструмент за разработка, да намери документация за всеки символ в кода по точно определени начин.

Това е функционалност на C# 5.0 и .NET Framework 4.0.

Двете ключови думи, които се използват при асинхронно програмиране са: модификаторът async и операторът await. Метод включващ модификаторът async се нарича асинхронен метод. Тези нови характеристики улесняват асинхронното програмиране. Например, при програмиране на Windows Forms, UI нишката ще бъде блокирана докато се използва HttpWebRequest. От гледна точна на потребителя, не може да се взаимодейства с форма преди заявката да е изпълнена.

private void btnTest_Click(object sender, EventArgs e) { 
    var request = WebRequest.Create(txtUrl.Text.Trim()); 
    var content=new MemoryStream(); 
    
    using (var response = request.GetResponse()) { 
        using (var responseStream = response.GetResponseStream()) { 
            responseStream.CopyTo(content); 
        } 
    } 
    txtResult.Text = content.Length.ToString(); 
}

Примерът показва, че след като натиснем Test бутона, не можем да направим никакви промени във формичката, преди резултата да се появи в текстовото поле.

Използването на метода BeginGetResponse е подобно на async метода, но е по-сложно.

Използване на async метод
private async void btnTest_Click(object sender, EventArgs e) { 
	var request = WebRequest.Create(txtUrl.Text.Trim()); 
	var content = new MemoryStream(); 
	Task<WebResponse> responseTask = request.GetResponseAsync(); 

	using (var response = await responseTask)  {
		using (var responseStream = response.GetResponseStream()) { 
			Task copyTask = responseStream.CopyToAsync(content); 
            // Операторът await се прилага върху върнатата задача. Той спира изпълнението на метода,
            // докато задачата е завършена. В същото време, контролът се връща на повикващия на отложения метод.
			await copyTask;
        } 
	} 
	txtResult.Text = content.Length.ToString(); 
}

Spec# е система за програмиране, която представлява разширение на езика C#. Повлияна е от JML, AsmL и Eiffel. Spec# включва структури като non-null типове, предусловия, следусловия, и обектни инварианти. Spec# е разработен от Microsoft Research.

  • Spec# позволява стабилна методология за програмиране, която позволява специфицирането и аргументирането на обектни инварианти дори в присъствието на callbacks и multi-threading.
  • Spec# е опит за по-ефективно разработване и поддръжката на висококачествен софтуер.
static void Main(string![] args) 
    requires args.Length > 0 {
    foreach (string arg in args) {
        ...
    }
}

Една от най-често срещаните грешки в обектно-ориентирани програми е причинена от разликата в null референцията. За да се избегне тази грешка Spec# прави разлика между ненулеви типове и вероятните нулеви типове. Non-null типове проверяват дали променливите от null тип не са null тип. Ако са null ще бъде хвърлен Exception.

string! input

Пример:

public Test(string! input) {
    ...
}

Предусловията се проверяват преди методът да бъде изпълнен.

public Test(int i)
    requires i > 0; 
{
        this.i = i;
}

Следусловията се проверяват след като методът бъде изпълнен.

public void Increment()
    ensures i > 0; 
{
        i++;
}

Проверка на изключения

[редактиране | редактиране на кода]

Spec# добавя проверени изключения като тези в Java.

public void DoSomething()
    throws SomeException; // SomeException : ICheckedException 
{
    ...
}

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

  1. а б в г Наков, Светлин. Въведение в програмирането със C# (pdf) // Софтуерен университет (СофтУни), 2018. с. 880. Посетен на 19 май 2024.
  2. а б Василев, Алексей. C# - основи на езика в примери. София, Асеневци, 2018. с. 594.
  3. C# Reference // Microsoft. Посетен на 19 май 2024. (на английски)
  4. Schildt, Herbert. C# 4.0 The Complete Reference. McGraw Hill Professional, 2010. ISBN 978-0-07-174117-0. p. 976. (на английски)
  5. а б Archer, Part 2, Chapter 4:The Type System
  6. C# Preprocessor Directives // C# Language Reference. Microsoft. Посетен на 18 юни 2009.
  7. Horton, Anson. C# XML documentation comments FAQ // 11 септември 2006. Посетен на 11 декември 2007.
  8. а б Delimiters for Documentation Tags // C# Programmer's Reference. Microsoft, 1 януари 1970 GMT. Посетен на 18 юни 2009.