Вы находитесь на странице: 1из 69

Основы C#

Урок 1. Что такое C#?....................................................................................................................3


Урок 2. Что такое NET Runtime?..................................................................................................4
Урок 3. Первая программа на C#.................................................................................................5
Урок 4. Переменные языка C#......................................................................................................7
Урок 5. Логические операторы.....................................................................................................9
Урок 6. Массивы в C#.................................................................................................................10
Урок 7. Операторы if и switch.....................................................................................................12
Урок 8. Циклы for и foreach........................................................................................................14
Урок 9. Цикл while.......................................................................................................................15
Урок 10. Классы в C#..................................................................................................................16
Урок 11. Конструкторы классов.................................................................................................18
Урок 12. Наследование................................................................................................................19
Урок 13. Запись в экземпляр базового класса экземпляра производного..............................21
Урок 14. Вложенные классы.......................................................................................................22
Урок 15. Перегрузка бинарных операторов..............................................................................23
Урок 16. Перегрузка унарных операторов................................................................................25
Урок 17. Область видимости......................................................................................................26
Урок 18. Виртуальные функции.................................................................................................27
Урок 19. Абстрактные классы....................................................................................................29
Урок 20. Запрещение наследования или модификатор sealed.................................................31
Урок 21. Статические переменные............................................................................................32
Урок 22. Статические методы класса........................................................................................33
Урок 23. Статические конструкторы.........................................................................................34
Урок 24. Закрытые конструкторы или классы без экземпляров.............................................35
Урок 25. Передача параметров переменой длины....................................................................37
Урок 26. Строки (класс System.String).......................................................................................38
Урок 27. Строки (класс StringBuilder).......................................................................................40
Урок 28. Передача параметров по ссылке и по значению (ref и out)......................................41
Урок 29. Пример передачи по ссылке........................................................................................42
Урок 30. Перегрузка....................................................................................................................43
Урок 31. Функции класса System.Array.....................................................................................45
Урок 32. Делегаты.......................................................................................................................46
Урок 33. События........................................................................................................................48
Урок 34. Пример класса с событием..........................................................................................50
Урок 35. Введение в атрибуты...................................................................................................52
Урок 36. Пример пользовательского атрибута.........................................................................53
Урок 37. Параметры командной строки....................................................................................55
Основы C#.

Урок 38. Метод Main в отдельном классе.................................................................................56


Урок 39. Форматированный вывод............................................................................................57
Урок 40. Класс System.Object.....................................................................................................58
Урок 41. Методы класса System.Object.....................................................................................59
Урок 42. Переопределяем методы класса System.Object.........................................................60
Урок 43. Константы.....................................................................................................................61
Урок 44. Модификаторы доступа...............................................................................................62
Урок 45. Ссылка на текущий экземпляр класса (this)..............................................................63
Урок 46. Класс Environment........................................................................................................64
Урок 47. Работаем со специальными папками.........................................................................65
Урок 48. Получаем список всех дисков.....................................................................................66
Урок 49. Ввод/вывод в C# (System.IO)......................................................................................67
Урок 50. Классы для работы с папками и файлами.................................................................68
Урок 51. Класс Directory.............................................................................................................69

2/69
Основы C#.

Урок 1. Что такое C#?


C# (произносится Си-Шарп) - это новый язык программирования от компании Microsoft.
Он входит в новую версию Visual Studio - Visual Studio .NET. Кроме C# в Visual
Studio.NET входят Visual Basic.NET и Visual C++. Кроме того фирма Borland объявила,
что последующие версии C++ Builder и Delphi будут поддерживать платформу .NET
(последнее лежит в русле политики Borland - так, например, нынешние версии C++ Builder
и Delphi поддерживают, например, такую технологию от Microsoft, как ActiveX).
Одна из причин разработки нового языка компанией Microsoft - это создание
компонентно-ориентированного языка для новой платформы .NET. Другие языки были
созданы до появления платформы .NET, язык же C# создавался специально под эту
платформу и не несет с собой груза совместимости с предыдущими версиями языков.
Хотя это не означает, что для новой платформы это единственный язык.
Еще одна из причин разработки компанией Microsoft нового языка программирования -
это создание альтернативы языку Java. Как известно, реализация Java у Microsoft не была
лицензионно чистой - Microsoft в присущей ей манере внесла в свою реализацию много
чего от себя. Компания Sun, владелица Java, подала на Microsoft в суд, и Microsoft этот суд
проиграла. Тогда Microsoft решила вообще отказаться от Java, и создать свой Java-
подобный язык, который и получил название C#. Что будет с Java после выхода C# - пока
неизвестно. Скорей всего эти языки будут существовать оба, хотя ясно, что одна из целей
разработки C# - это противоборство именно с Java (недаром C# называют еще Java-killer).

3/69
Основы C#.

Урок 2. Что такое NET Runtime?


Если перевести слова NET Runtime на русский язык, то мы получим что-то вроде "Среда
выполнения". Именно вы этой среде и выполняется код, получаемый в результате
компиляции программы написанной на C#. NET Runtime основан не на ассемблере (т. е.
не на коде, родном для процессора), а на некотором промежуточном коде. Отдаленно он
напоминает виртуальную Java машину. Только если в случае Java у нас был только один
язык для виртуальной машины, то для NET Runtime таких языков может быть несколько.
Теоретически программа для среды NET Runtime может выполняться под любой
операционной системой, в которой NET Runtime установлена. Но на практике пока
единственная платформа для этого - это Windows.
NET Runtime состоит из нескольких частей. Одна из них - это Common Language Runtime.
Это, говоря кратко, это некоторый набор стандартов, которые должны поддерживать все
языки платформы .NET. Например, в предыдущих версиях Visual Studio была такая
проблема, что разные языки по разному хранили данные одного по идее типа. Так,
скажем, тип целого в Visual Basic занимал два байта, а в Visual C++ - четыре. А это
порождало кучу проблем при совместном использовании языков. Так вот, Common
Language Runtime как раз в частности и определяет стандартные для все языков .NET
типы данных. И уже есть гарантии, что целый тип в одном языке будет в точности
соответствовать одноименному типу в другом.
Еще одна важная часть NET Runtime - это набор базовых классов. Их очень много
(порядка несколько тысяч). Кроме того, эти классы относятся не к конкретному языку, а к
NET Runtime. Т. е. мы получаем набор классов, общий для всех языков .NET, что
достаточно удобно.
Далее. Именно NET Runtime берет на себя некоторые рутинные функции. Например в нем
организована сборка мусора. И если раньше программисту приходилось самому
освобождать объекты, созданные динамически, то теперь эту задачу берет на себя среда
NET Runtime. Еще одно свойство среды NET Runtime - это проверка типов. Означает это
вот что. Когда программа выполняется, то в принципе некоторой функции можно
подсунуть параметр неправильного типа. Скажем вместо целого подставить
действительное число или еще что-нибудь в этом роде. Языки типа C++ свои параметры
функций не проверяют, в результате чего записанная переменная большего размера может
повредить чужую область памяти и программа может просто рухнуть. Еще классический
пример на эту тему - это выход за пределы массива. В NET Runtime же такого
невозможно. NET Runtime сама позаботится о проверке типов и других вещах.
Существует несколько языков для NET Runtime. В настоящее время это C#, VB.NET и
Visual C++. Кроме того фирма Borland объявила, что ее продукты C++ Builder и Delphi
тоже будут поддерживать NET Runtime.

4/69
Основы C#.

Урок 3. Первая программа на C#


Ну, хватит нам заниматься теорией. Пора переходить к практике. Пишем первую
программу на C#. Запускайте Visual Studio.NET. Для создания нового пустого проекта C#
нажимаем на кнопку в New Project (самая левая на панели инструментов), или нажимаем
комбинацию клавиш Ctrl + Shift + N, или просто заходим в меню File и далее выбираем
New и затем Project:

В появившемся окне New Project слева выбираем, естественно, Visual C#, а справа тип
приложения - Console Application:

В качестве имени проекта (Name) напечатайте first или что-то в этом роде. Нажмите на
кнопку для закрытия данного диалогового окна.
Теперь приступаем к коду. Наша первая программа просто выведет некоторое
фиксированное слово в консольное окошко. Вот ее листинг.
using System;

namespace first
{
///
/// Summary description for Class1.
///
class Class1
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
Console.WriteLine("Привет из C#");
}
}
}
Запускаем программу, нажав Ctrl+F5. Результат будет таким:

Разберем текст программы поподробнее. Как вы знаете из предыдущего урока, в .NET


Runtime существуют пространства имен. Одно из таких пространств - это System. Вообще-
то оно добавляется автоматически в любой проект на C#. Так как мы добавили в нашу
программу строчку

5/69
Основы C#.

...
using System;
...
то мы можем вместо длинных имен использовать более короткие. В частности, вместо
System. Console можно писать просто Console. Что мы делаем в строчке
...
Console.WriteLine("Привет из C#");
...

Далее мы в нашей программе объявляем класс Class1. Что такое классы мы посмотрим в
последующих уроках, сейчас же достаточно сказать, что в C# не существует глобальных
функций, так что нам ничего не остается, как завести сначала класс и затем функцию Main
в нем (функция Main обязательно должна быть в каждой программе на C#, и именно с
этой функции и начинается выполнение программы. Обратите также внимание, что эта
функция пишется с прописной (большой) буквы. C# различает строчные и прописные
буквы, так что это важно). Кроме того, эта функция объявлена с модификатором static.
Это означает, что она не относится к конкретному экземпляру класса Class1, а
принадлежит всему классу. В нашей функции Main мы просто выводим на экран
некоторую строчку методом WriteLine.
С первой программой на C# все.

6/69
Основы C#.

Урок 4. Переменные языка C#


Для каждого типа данных C# существует соответствующий тип данных в CRL (Common
Language Runtime). Подробности CRL см. в уроке 2. Это, в частности, означает, что
каждый тип имеет два названия - полный (из CLR, его можно использовать в любом языке
.NET) и сокращенный, который используется в C#. Что за название использовать - это
дело вкуса. Но короткие названия во-первых, короче, и во-вторых, как-то привычнее, так
что мы в наших уроках будем использовать короткие названия C#. На этом же уроке мы
приведем как полные названия (из CLR), так и краткие.
Основные типы данных перечислены в следующей таблице:
Тип C# Тип CLR Размер в байтах Пояснение
int Int32 4 Целое (со знаком)
float Single 4 Вещественное число
char Char - Символ (Unicode)
bool Boolean - Логический тип
short Int16 2 Короткое целое (со знаком)
long Int64 8 Длинное целое (со знаком)
string String - строка
byte Byte 1 байт
decimal Decimal 8 Вещественное число фиксированной точности
Если вы предпочитаете использовать длинные имена, то вы должны писать что-то вроде
System.Int32 для типа int и т. п. (т. е. приписывать слово System перед CLR-именем). Или
же вы должны добавить строчку
using System;

в начале программы (хотя, впрочем, она там скорей всего и так есть).
Таким образом следующие три объявления переменной k равносильны:
int k;
using System;
...
Int32 k;

и
System.Int32 k;
Разумеется, аналогично мы имеем дело и другими типами языка C#.
Объявление переменной можно совместить с инициализацией (заданием начального
значения):
int z=88;

Набор операторов для C# достаточно стандартен + , -, *, / - действуют как и в любом


другом языке. Отметим только, что / (деление) применительно к целым числам дает
целую часть от деления. Так, фрагмент
int k=100999, n=1000, s;
s=k/n;
Console.WriteLine(s.ToString());
выведет на экран 100, а не 101, т. е. никакого округления не происходит.
Есть еще один оператор - %. Это - остаток от деления. Следующий фрагмент выведет на
экран 999:

7/69
Основы C#.

int k=100999, n=1000, s;


s=k%n;
Console.WriteLine(s.ToString());
Как и в других C-подобных языках, в C# существуют операторы инкремента и
декремента. Так, после следующего фрагмента k увеличится на 1, а n - уменьшится на 1:
k++;
n--;

8/69
Основы C#.

Урок 5. Логические операторы


Как и в других C-подобных языках, в C# существуют следующие логические операторы:
Оператор Описание Пример
Логическое И. Результат равен true, только если оба
&& (x==8) && (y==5)
операнда равны true
Логическое ИЛИ. Результат равен false, только если оба
|| (y>8) || (y<5)
операнда равны false
Отрицание. Изменяет логическое значение на
! if(!(a==b))...
противоположное
Все эти операторы возвращают результат типа bool.
Обратите внимание, что для логического равно (т. е. для ответа на вопрос "Верно ли, что
что-то равно чему-то") используется знак двойного равенства (==). Знак же одинарного
равенства (=) используется для присваивания. Для знака == существует парный знак !=
("не равно"). Так, приведенный выше пример для оператора ! можно переписать так:
if(!(a==b))...

Отметим, что в C#, в отличии от многих других языков программирования, нельзя вместо
false использовать 0, а вместо true - любое ненулевое число. Так, следующий фрагмент
содержит ошибку:
int k;
...
if(k) //Ошибка!
...

9/69
Основы C#.

Урок 6. Массивы в C#
Массивы в C# несколько отличаются от других C-подобных языков. Начнем сразу с
примеров. Пример первый:
...
int[] k; //k - массив
k=new int [3]; //Определяем массив из 3-х целых
k[0]=-5; k[1]=4; k[2]=55; //Задаем элементы массива
//Выводим третий элемент массива
Console.WriteLine(k[2].ToString());
...

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


особенности. Во-первых, массив определяется именно как
int[] k;
а не как один из следующих вариантов:
int k[]; //Неверно!
int k[3]; //Неверно!
int[3] k; //Неверно!

Во-вторых, так как массив представляет из себя ссылочный объект, то для создания
массива необходима строка
k=new int [3];
Именно в ней мы и определяем размер массива. Хотя, вообще говоря, возможны
конструкции вида
int[] k = new int [3];

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


int[] k = {-5, 4, 55};
Разумеется, приведенные конструкции применимы не только к типу int и не только к
массиву размера 3.
В C#, как и в C/C++, нумерация элементов массива идет с нуля. Таким образом в нашем
примере начальный элемент массива - это k[0], а последний - k[2]. Элемента k[3],
разумеется, нет.
Теперь переходим к многомерным массивам. Вот так задается двумерный массив:
int[,] k = new int [2,3];

Обратите внимание, что пара квадратных скобок только одна. Естественно, что в нашем
примере у массива 6 (=2*3) элементов (k[0,0] - первый, k[1,2] - последний).
Аналогично мы можем задавать многомерные массивы. Вот пример трехмерного массива:
int[,,] k = new int [10,10,10];
А вот так можно сразу инициализировать многомерные массивы:
int[,] k = {{2,-2},{3,-22},{0,4}};

Приведенные выше примеры многомерных массивов называются прямоугольными. Если


их представить в виде таблицы (в двумерном случае), то массив будет представлять из
себя прямоугольник.
Наряду с прямоугольными массивами существуют так называемые ступенчатые. Вот
пример:
//Объявляем 2-мерный ступенчатый массив
int[][] k = new int [2][];
//Объявляем 0-й элемент нашего ступенчатого массива

10/69
Основы C#.

//Это опять массив и в нем 3 элемента


k[0]=new int[3];
//Объявляем 1-й элемент нашего ступенчатого массива
//Это опять массив и в нем 4 элемента
k[1]=new int[4];
k[1][3]=22; //записываем 22 в последний элемент массива
...
Обратите внимание, что у ступенчатых массивов мы задаем несколько пар квадратных
скобок (столько, сколько размерность у массива). И точно так же мы что-нибудь делаем с
элементами массива - записываем, читаем и т. п.
Самая важная и интересная возможность у ступенчатых массивов - это их "не
прямоугольность". Так, в приведенном выше примере в первой "строке" массива k три
целых числа, а во второй - четыре. Часто это оказывается очень к месту.

11/69
Основы C#.

Урок 7. Операторы if и switch


If служит для разветвления программы на два направления. Если некоторое условие
выполняется, то программа идет в одну сторону, если не выполняется - то в другую. Вот
сразу пример, определяющий, четное или нечетное число ввел пользователь:
...
class Class1
{
...
static void Main(string[] args)
{
int k = Int32.Parse(Console.ReadLine());
if(b)
{
Console.WriteLine("Четное число");
}
else
{
Console.WriteLine("Нечетное число");
}
Console.ReadLine();
}
}
Как и в других C-подобных языках, фигурные скобочки можно не писать в случае одного
оператора. Также написание веточка else тоже не является необходимым - все зависит от
конкретной задачи.
Оператор switch примеряется тогда, когда программа должна разделится более чем на два
направления (т. е. будем двигаться или сюда, или сюда, или сюда). Вот пример:
int k = Int32.Parse(Console.ReadLine());
Console.WriteLine(k.ToString());
switch (k){
case 1:
case 2:
Console.WriteLine("Неудовлетворительно");
break;
case 3:
Console.WriteLine("Удовлетворительно");
break;
case 4:
Console.WriteLine("Хорошо");
break;
case 5:
Console.WriteLine("Отлично");
break;
default:
Console.WriteLine("Ошибка");
break;
}
В приведенном примере в зависимости от введенного пользователем числа на экран
выводится та или иная оценка. Если число k не лежит в промежутке от 1 до 5, то
выполняются операторы в веточке default и выводится надпись "Ошибка". Ветка default не
обязательна. Обратите внимание на оператор break. Если его не написать, то будут
выполнятся операторы из следующей веточки case до строки с break (т. е. в данном
примере если пользователь введет 1 или 2, то программы выведет
"Неудовлетворительно"). Обратите внимание, что если в некоторой веточке case или
default есть операторы, то написание break обязательно. Так, в следующих двух кусках
кода есть ошибки:
...
case 1:

12/69
Основы C#.

Console.WriteLine("Совсем неудовлетворительно");
//Ошибка! Тут пропушен break
case 2:
Console.WriteLine("Неудовлетворительно");
break;
...
...
default:
Console.WriteLine("...");
//Ошибка! Тут пропушен break
}

13/69
Основы C#.

Урок 8. Циклы for и foreach


Начнем сразу с примера цикла for:
int k = Int32.Parse(Console.ReadLine());
int sum=0;
for(int i=1; i<=k; i++){
sum+=i;
}
Console.WriteLine(sum);
Этот пример подсчитывает сумму чисел от 1 до введенного пользователем числа k. Сумма
записывается в переменную sum и выводится на экран.
Очень часто циклы используются для некоторых действий с массивами. Так как
нумерация элементов массива идет с нуля, то типичный цикл будет выглядеть так:
int[] a = {-5, 4, 55};
int sum=0;
for(int i=0; i<3; i++){
sum+=a[i];
}

В этом примере начальное значение для счетчика цикла равно нулю, и в условии
продолжения цикла мы пишем знак "меньше", после которого ставится количество
элементов в массиве. Разумеется, если в цикле должен выполнится только один оператор,
то фигурные скобки можно не писать. Тут все, как в других C/C++-подобных языках.
Теперь рассмотрим пример цикла foreach:
int[] m = {-5, 4, 10};
int sum=0;
foreach(int i in m){
sum+=i;
}
В данном примере мы суммируем все элементы массива m, записывая сумму в sum.
В приведенном примере наш цикл перебирает все элементы массива m. На это нам
указывает строка
...
foreach(int i in m){
...

которая интерпретируется так: для каждого целого числа из массива m делам что-то там.
Если бы элементами массива были бы не целые, а, скажем, вещественные, то мы записали
бы что-то вроде:
...
foreach(float i in m){
...
Т. е. мы пишем именно тип элементов массива. На самом деле foreach используется не
только для массивов, но и для других объектов (например, для хэш-таблиц). Но это будет
рассмотрено в последующих уроках.

14/69
Основы C#.

Урок 9. Цикл while


Циклы while бывают двух видов - собственно цикл while и do-while. Оба эти цикла
используются, как правило, тогда, когда точно не известно, сколько раз цикл должен
выполнится. Например, при вводе пользователем пароля или при подсчете чего-либо с
определенной точностью. Оба эти цикла будут выполняться до тех пор, пока условие в
круглых скобках после слова while будет истинно. Как только условие станет равным
false, выполнение цикла прекращается. Самое важное отличие между while и do-while в
том, что while может не выполниться ни одного раза, тогда как do-while по крайней мере
один раз выполнится. Вот примеры их использования:
string password;
do{
password=Console.ReadLine();
}while(password!="wi98zK");
int k=0; //Количество попыток
//заводим новую последовательность случайных чисел
Random rnd=new Random(112); //Пишем любой параметр
while(rnd.Next(1, 6)!=5)
{
k++;
};
Console.WriteLine("С "+(k+1).ToString()+"-го раза выпало 5");
В первом примере цикл будет вращаться до тех пор, пока пользователь не введет
правильный пароль (wi98zK), во втором - пока некоторое случайное число не окажется
равным 5. При этом если число с самого начала оказалось равным пяти, то цикл вообще
выполняться не будет.

15/69
Основы C#.

Урок 10. Классы в C#


Начиная с этого урока, приступаем к изучению классов.
Сначала пару слов о том, что такое классы. Представьте себе, что у вас есть некоторый
объект, который характеризуется рядом свойств. Например, работник на некой фирме. У
него есть такие свойства, как фамилия, возраст, стаж и т. п. Так вот, в этом случае удобно
каждого работника описывать не рядом независимых переменных (строкового типа для
фамилии, целого типа для возраста и стажа), а одной переменной типа Worker, внутри
которой и содержаться переменные для фамилии, возраста и стажа. Это здесь самое
важное - что в переменной типа Worker содержаться другие переменные. Конечно, типа
Worker среди встроенных типов данных нет - ну так эта не беда - мы можем ввести его.
Еще одна важная вещь про классы - это то, что в классах помимо переменных разных
типов содержатся функции (или, что тоже самое, методы) для работы с этими
переменными. Скажем, в нашем примере с классом Worker логично ввести специальные
функции для записи возраста и стажа. Функции будут, в частности, проверять
правильность вводимой информации. Например, ясно, что возраст у работника не может
быть отрицательным или большим 150 ;). Так вот наша функция и будет проверять
правильность введенного пользователем возраста.
Давайте рассмотрим первый пример класса. Создайте новое консольное приложение для
C# и введите следующий текст:
using System;
namespace test
{
//Начало класса
class Worker
{
public int age=0;
public string name;
}
//Конец класса

class Test
{
[STAThread]
static void Main(string[] args)
{
Worker wrk1 = new Worker();
wrk1.age=34;
wrk1.name="Иванов";
Console.WriteLine(wrk1.name+", "+wrk1.age);
}
}
}

Запустите программу. Она, как и следовало ожидать, выведет на экран "Иванов, 34".
Давайте кратко обсудим наш код. Во-первых, в строчках
...
class Worker
{
public int age=0;
public string name;
}...

мы определили наш класс Worker. Внутри этого класса существует две переменные -
целая age для возраста и name строкового типа для имени. Обратите внимание, что, в
отличие от C/C++, мы можем задавать некоторое начальное значение прямо сразу после
объявления переменной:

16/69
Основы C#.

...
public int age=0;
...
Начальное значение задавать вовсе не обязательно - это видно по переменной name.
Перед переменными мы пишем ключевое слово public. Значение у него такое же, как и в
C++ - а именно это означает, что эта переменная (или функция) будет видна вне класса.
Если мы не напишем перед переменной никакого модификатора доступа, или напишем
private, то переменная не будет видна снаружи класса и ее смогут использовать только
функции этого же класса (т. е. она будет для "внутреннего использования").
Далее в строчке
...
Worker wrk1 = new Worker();
...

мы заводим экземпляр класса в куче (heap) и возвращаем на него ссылку.


Затем в строчках
...
wrk1.age=34;
wrk1.name="Иванов";
Console.WriteLine(wrk1.name+", "+wrk1.age);
...
мы используем наш класс. А именно присваиваем некоторые значения для возраста и
имени и выводим их потом на экран.
С этим уроком все.

17/69
Основы C#.

Урок 11. Конструкторы классов


Для начала кратко обсудим, что такое конструктор вообще. Конструктор - это функция
(метод) класса. Раз эта функция, то описываем мы ее почти точно так же, как и любую
другую функцию класса - пишем параметры в круглых скобках и т. п. Когда конструктор
вызывается? В момент создания переменной. При этом у класса может быть несколько
конструкторов - но при этом они должны различаться или типом параметров, или их
количеством.
Этот урок будет основан на предыдущем. Откройте проект, созданный на прошлом уроке.
Давайте добавим в наш класс Worker конструктор:
...
class Worker
{
public int age=3;
public string name;
//Конструктор 1
public Worker(int age, string name)
{
this.age=age;
this.name=name;
}
}
...

Обратите внимание на ряд особенностей конструктора. Во-первых, конструктор


называется как класс. Раз наш класс называется Worker, значит и конструктор должен
называться точно также. И во-вторых, конструктор, в отличие от других методов класса,
не возвращает никакого значения (даже типа void). Если вы знакомы с языком C++, то,
несомненно, это вам все знакомо.
Что делает наш конструктор? Он записывает передаваемые в него параметры во
внутренние переменные класса. Обратите внимание, что называются они одинаково - age
и age, name и name. Компилятор сначала ищет локальную переменную с таким именем, и,
если не находит, то переменную класса. Поэтому age (и name) - это передаваемый в
конструктор параметр. Если же нам надо сослаться на переменную класса (при
существовании переменной с таким же именем, как и передаваемый в функцию
параметр), то мы используем ключевое слово this. Оно всегда указывает на текущий
экземпляр нашего класса. Таким образом в строчках
...
this.age=age;
this.name=name;
...

мы передаем параметры конструктора в переменные класса.


Теперь изменим тестовый класс test следующим образом:
...
static void Main(string[] args)
{
//Вызываем конструктор
Worker wrk1 = new Worker(40, "Вася");
Console.WriteLine(wrk1.name+", "+wrk1.age);
}
...

Теперь после объявления переменной мы вызываем конструктор с двумя параметрами,


который запишет в переменные age и name экземпляра класса Worker значения 40 и
"Вася". Строчкой ниже эти значения выводятся на экран.

18/69
Основы C#.

Урок 12. Наследование


Представьте себе, что у нас есть некоторый класс (быть может весьма большой), который
почти подходит для нашей конкретной задачи. Почти, но не совсем. Что-то (некоторые
методы) в этом классе надо изменить, что-то - добавить. Наследование как раз и служит
для этого. При наследовании мы объявляем наш класс потомком другого класса. И наш
класс-потомок (или, как его еще называют, дочерний класс) автоматически приобретает
всё, что было в родительском классе. Это мы делаем буквально парой строчек. Затем в
дочерний класс мы можем что-нибудь добавить (чего не было в классе родительском).
Кроме того, в дочернем классе мы можем что-нибудь из родительского класса
переопределить - так что оно уже будет работать по-другому.
Давайте рассмотрим это все на примере. У нас будет класс Worker и производный от него
класс Boss. Класс Boss будет отличаться от класса Worker во-первых, наличием
переменной numOfWorkers (для количества подчиненных) и, во вторых, метод для
задания возраста (setAge) будет работать в Boss не так:
using System;
namespace test
{
//Класс Worker
class Worker
{
protected int age=0;
public void setAge(int age)
{
if(age>0 && age<100)
this.age=age;
else
this.age=0;
}
public int getAge()
{
return age;
}
}
//Класс Boss
class Boss : Worker{
public int numOfWorkers; //Количество подчиненных
public new void setAge(int age)
{
if(age>0 && age<45)
this.age=age;
else
this.age=0;
}
}
class Test
{
static void Main(string[] args)
{
Worker wrk1 = new Worker();
Boss boss = new Boss();
wrk1.setAge(50);
boss.setAge(50);
boss.numOfWorkers=4;
Console.WriteLine("Возраст работника "+wrk1.getAge());
Console.WriteLine("Возраст босса "+boss.getAge()+
".\nКоличество подчиненных "+boss.numOfWorkers);
}
}
}

Обратите внимание, как мы вводим новый класс Boss:

19/69
Основы C#.

...
class Boss : Worker{
...
Такая запись и означает, что новый класс Boss будет потомком другого класса (Worker в
данном случае). Так как наш класс - потомок другого класса, то он автоматически умеет
все тоже самое, что и родительский класс. В частности, в нем есть переменная age для
хранения возраста. Далее, в класс Boss мы вводим переменную numOfWorkers для
хранения количество подчиненных у нашего босса. В родительском классе такой
переменной не было. Кроме того, нас не устроила реализация метода setAge из
родительского класса Worker (по каким-то там внутренним инструкциям фирмы возраст
босса не может превышать 45 лет, тогда как возраст работника не может превышать 100),
поэтому мы просто написали в классе Boss метод с таким же именем. Обратите внимание
на слово new при объявлении метода:
...
public new void setAge(int age)
...

Таким образом мы в производном классе можем что-нибудь добавлять и что-нибудь


изменять по отношению к классу родительскому. Обратите внимание, что убирать что-
нибудь, полученное в наследство от родительского класса, мы не можем.
При запуске наша программа даст вполне ожидаемый результат - в возраст работника
запишется 50, в возраст босса - 0.

20/69
Основы C#.

Урок 13. Запись в экземпляр базового класса экземпляра


производного
В переменной типа базового класса можно хранить и переменные производных классов.
Например, текст метода Main из класса Test прошлого урока можно переписать
следующим образом:
...
static void Main(string[] args)
{
Worker wrk1 = new Boss();
...
Здесь мы объявили переменную wrk1 типа Worker. Вот другой пример на эту же тему:
Worker wrk1;
Boss boss = new Boss();
wrk1=boss;
...
Здесь мы опять записываем в переменную wrk1 (типа Worker) переменную boss (типа
Boss).
Но, несмотря на то, что мы записываем в переменную wrk1 или с помощью оператора new
или посредством присваивания экземпляр производного класса, все равно методы
производного класса мы использовать не можем (на самом деле из производного класса
мы не сможем использовать не только методы, но и переменные)!
Например, в следующие строки выведут на экран 50, а не ноль:
wrk1.setAge(50); //Вызывается метод класса Worker, а не класса Boss
Console.WriteLine("Возраст работника " + wrk1.getAge());
Как с этим бороться, мы рассмотрим на следующем уроке, а пока поясним, почему это
может оказаться важным. Например, в вашей фирме есть работники разных типов -
программисты, директора, бухгалтера и т. п. Так как у все категорий работников есть что-
то общее, что вы можете создать базовый класс Worker и производные от него
специализированные классы. Далее вы можете в вашей программе объявить массив типа
Worker. Так вот, в этом массиве можно будет хранить всех работников (т. е. экземпляры
всех классов, производных от класса Worker). И это удобно - одним циклом вы сможете
начислить всем зарплату и т. п.

21/69
Основы C#.

Урок 14. Вложенные классы


Иногда для некоторый класс играет чисто вспомогательную роль для другого класса и
используется только внутри него. В этом случае логично описать его внутри
существующего класса. Вот пример такого описания:
using System;
namespace test
{
class ClassA
{
//Вложенный класс
private class ClassB
{
public int z;
}
//Переменная типа вложенного класса
private ClassB w;
//Конструктор
public ClassA()
{
w=new ClassB();
w.z=35;
}
//Некоторый метод
public int SomeMethod()
{
return w.z;
}
}
class Test
{
static void Main(string[] args)
{
ClassA v=new ClassA();
int k=v.SomeMethod();
Console.WriteLine(k);
}
}
}

Здесь класс ClassB объявлен внутри класса ClassA. Объявлен он со словом private, так что
его экземпляры мы можем создавать только внутри класса ClassA (что мы и делаем в
конструкторе класса ClassA). Методы класса ClassA имеют доступ к экземпляру класса
ClassB (как, например, метод SomeMethod).
Вложенный класс имеет смысл использовать тогда, когда его экземпляр используется
только в определенном классе. Кроме того, при использовании вложенных классов
улучшается читаемость кода - если нас не интересует устройство основного класса, то
разбирать работу вложенного класса нет необходимости.
Обратите также внимание, как вложенные классы показываются на вкладке ClassView:

22/69
Основы C#.

Урок 15. Перегрузка бинарных операторов


Перегрузка операторов - это задание для известных операторов (например, для +) новых
значений. Например, мы можем перегрузить оператор + для векторов так, чтобы стало
возможным использование в нашей программе конструкции вида:
...
Vector a, b, c;
...
c = a + b;
...

Здесь Vector - это введенный нами класс. Если мы не сделаем перегрузку операторов, то
мы не сможем использовать знак "плюс" для экземпляров нашего класса Vector. Вот как
это можно сделать для бинарного (т. е. имеющего два параметра) оператора:
using System;
namespace test
{
class Vector
{
float x, y; //Координаты
//Конструктор
public Vector(float x, float y)
{
this.x=x;
this.y=y;
}
//Чтение x-координаты
public float GetX()
{
return x;
}
//Чтение y-координаты
public float GetY()
{
return y;
}
//Перегрузка оператора +
public static Vector operator +(Vector w, Vector v)
{
Vector res = new Vector(w.x + v.x, w.y + v.y);
return res;
}
}
class Test
{
static void Main(string[] args)
{
Vector a=new Vector(1, 2), b=new Vector(2, -1), c;
c = a + b;
Console.WriteLine("x=" + c.GetX() + ", "
+ "y=" + c.GetY());
}
}
}
Код для нашего класса приведен полностью, хотя перегрузка оператора занимает всего
несколько строк. Вот они:
...
public static Vector operator +(Vector w, Vector v)
{
Vector res = new Vector(w.x + v.x, w.y + v.y);
return res;
}

23/69
Основы C#.

...
Синтаксис здесь такой - если мы перегружаем оператор op, то мы должны в качестве
имени оператора написать operator op (в нашем случае мы перегружаем +, поэтому пишем
operator +). Далее, раз это метод, то мы пишем в круглых скобках его параметры. Для
бинарного оператора их два (в отличие от C++, где параметр - это только правый операнд,
левый же передается неявно - через указатель this). У нас они оба имеют тип Vector. Перед
operator op пишем тип возвращаемого значения. Так как сумма двух векторов - это опять
вектор, то перед именем нашего метода пишем Vector. Модификатор доступа public
понятен - без него мы бы не смогли использовать перегруженный оператор вне класса. А
вот слово static важно - в C# перегруженные операторы идут как static. Не забывайте его
писать.
Внутри перегруженного оператора мы заводим переменную res типа Vector для возврата
через нее результата. Ее мы определяем через конструктор с двумя параметрами, который
мы ранее написали для нашего класса. Сложение двух векторов происходит так, как и
должно происходить – по координатно.
В классе test мы испытываем наш перегруженный оператор. Все должно работать как и
положено, и результат сложения должен вывестись на консоль в виде "x=3, y=1".

24/69
Основы C#.

Урок 16. Перегрузка унарных операторов


Унарные операторы - это те, которые имеют только один операнд (параметр). Типичные
примеры унарных операторов - это унарный минус (меняющий знак на
противоположный) и оператор инкремента ++.
Рассмотрим перегрузку унарных операторов на примере рассмотренного на прошлом
уроке класса Vector. А именно, добавим в наш класс унарный минус, который будет
менять вектор с координатами x и y на вектор с координатами -x и -y. Для этого добавьте
в наш класс Vector следующие строки:
public static Vector operator -(Vector v)
{
Vector res = new Vector(-v.x, -v.y);
return res;
}

Обратите внимание, что при перегрузке унарных операторов (как, впрочем, и при
перегрузке бинарных), мы должны поставить модификатор static.
Для испытания перегруженного оператора изменить класс Test приблизительно
следующим образом:
class Test
{
static void Main(string[] args)
{
Vector a=new Vector(1, 2), b;
b = -a;
Console.WriteLine("x=" + b.GetX() + ", "
+ "y=" + b.GetY());
}
}

Как и следовало ожидать, результатом выполнения программы будет вывод на экран


строки "x=-1, y=-2".
Не надо думать, что при перегрузке операторов тип возвращаемого значения всегда
совпадает с типом класса (для которого происходит перегрузка). Это зависит от
конкретной задачи. Вот пример перегрузки оператора !, который возвратит модуль
вектора:
public static double operator !(Vector v)
{
double res = Math.Sqrt(v.x*v.x + v.y*v.y);
return res;
}
Здесь мы воспользовались методом Sqrt класса Math для возврата квадратного корня. Sqrt
- это статический метод класса Math, поэтому мы вызываем его не для конкретного
экземпляра класса, а для самого класса. О статических методах мы поговорим позже, пока
же измените тестовый класс Test следующим образом:
class Test
{
static void Main(string[] args)
{
Vector a=new Vector(3, 4);
Console.WriteLine(!a);
}
}
Наша программа должны вывести 5 (длину вектора (3, 4)).

25/69
Основы C#.

Урок 17. Область видимости


Переменные существуют только в своей области видимости, при выходе из которой
переменная "умирает". Область видимости переменной в первом приближении начинается
в строке, где переменная объявлена и кончается на закрывающей фигурной скобке
(переменная должна быть объявлена между этой скобкой и парной к ней открывающей).
Вот поясняющий пример:
int k=3;
//Все OK, переменая k видна
Console.WriteLine(k);

Тут значение переменной k доступно и выведется на экран.


...
{
int k=3;
}
//Переменная k не видна
Console.WriteLine(k); //Ошибка!
В этом же примере вывести k на экран не получится - она умерла на закрывающей
фигурной скобке.
Во вложенных областях видимости мы, как правило, не можем объявлять переменные с
одинаковыми именами:
int k=4;
{
//Ошибка!
int k=3;
}

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


class Vector
{
float x, y; //Координаты
...
public Vector(float x, float y)
{
this.x=x;
this.y=y;
}
Здесь мы имеем две пары одноименных переменных - x в параметре конструктора и x как
переменная класса. Так как для уточнения переменной класса мы можем использовать
this, то ошибки не будет.

26/69
Основы C#.

Урок 18. Виртуальные функции


Как показано в уроке 13 переменная базового класса может использоваться для хранения
переменных производных классов. Но при этом мы не сможем использовать методы из
производного класса. Сейчас мы с вами и посмотрим, как эту неприятность можно
обойти. Для этого как раз и служит механизм виртуальных функций.
Если в родительском классе некоторая функция объявлена как виртуальная, то в
производном классе ее можно переопределить. В этом, собственно говоря, ничего нового
нет - атомы могли делать и без всяких виртуальных функций. Новое заключается в том,
что если мы запишем в переменную типа родительского класса экземпляр производного,
то для такого экземпляра мы сможем вызывать переопределенную функцию производного
класса. Вот пример, поясняющий это:
using System;
namespace test
{
//Класс Worker
class Worker
{
protected int age=0;
virtual public void setAge(int age)
{
if(age>0 && age<100)
this.age=age;
else
this.age=0;
}
public int getAge()
{
return age;
}
}
//Класс Boss
class Boss : Worker
{
public int numOfWorkers; //Количество подчиненных
override public void setAge(int age)
{
if(age>0 && age<45)
this.age=age;
else
this.age=0;
}
}
class Test
{
static void Main(string[] args)
{
Worker boss = new Boss();
boss.setAge(50);
Console.WriteLine("Возраст босса "+boss.getAge());
}
}
}

Как вы видите, тут функцию setAge в родительском классе Worker мы определили с


ключевым словом virtual, а одноименную функцию в производном классе Boss - с
ключевым словом ovеrride.

27/69
Основы C#.

Обратите внимание на то, что из какого конкретно класса вызывается функция (из
родительского или производного) определяется на этапе выполнения программы, а не на
этапе компиляции. В принципе в переменную родительского типа мы могли бы записать
экземпляр именно родительского класса. В этом случае, естественно, вызвалась бы
функция родительского класса. Вот поясняющий это утверждение пример:
class Test
{
static void Main(string[] args)
{
Worker boss;
bool b;
//Присваиваем значение в переменную b
...
if(b)
{
//В переменной boss - экземпляр класса Boss
boss=new Boss();
}
else
{
//В переменной boss - экземпляр класса Worker
boss=new Worker();
}
//Вызываем метод класса Boss или Worker
boss.setAge(50);
Console.WriteLine("Возраст "+boss.getAge());
}
}
С этим уроком все!

28/69
Основы C#.

Урок 19. Абстрактные классы


Методы класса могут быть объявлены как абстрактные. Это означает, что в этом классе
нет реализации этих методов. Абстрактные методы пишутся с модификатором abstract.
Класс, в котором есть хотя бы один абстрактный метод, называется абстрактным (а таком
классе могу быть и обычные методы). Нельзя создавать экземпляры абстрактного класса -
такой класс может использоваться только в качестве базового класса для других классов.
Для потомка такого класса есть две возможности - или он реализует все абстрактные
методы базового класса (и в этом случае для такого класса-потомка мы сможем создавать
его экземпляры), или он реализует не все абстрактные методы базового класса (в этом
случае он является тоже абстрактным классом и единственная возможность его
использования - это производить от него классы-потомки). Вот пример, иллюстрирующий
использование абстрактных классов:
using System;
namespace test
{
abstract class Figure
{
//Площадь фигуры
public abstract double square();
public abstract double perimeter();
}
class Triangle: Figure
{
double a, b, c; //Стороны
//Конструктор
public Triangle(double a, double b, double c)
{
this.a=a;
this.b=b;
this.c=c;
}
public override double square()
{
//Используем формулу Герона
double p = (a+b+c)/2;
return Math.Sqrt(p*(p-a)*(p-b)*(p-c));
}
public override double perimeter()
{
return a+b+c;
}
}
class Rectangle: Figure
{
double a, b; //Стороны
//Конструктор
public Rectangle(double a, double b)
{
this.a=a;
this.b=b;
}
public override double square()
{
return a*b;
}
public override double perimeter()
{
return (a+b)*2;
}
}
class Test
{

29/69
Основы C#.

public static void Main()


{
Figure f1, f2;
f1=new Triangle(3, 4, 5);
f2=new Rectangle(2, 6);
System.Console.WriteLine(f1.perimeter()+", "+ f1.square());
System.Console.WriteLine(f2.perimeter()+", "+ f2.square());
}
}
}

Тут мы объявляем абстрактный класс Figure, от которого производим два класса -


Rectangle (класс прямоугольника) и Triangle (треугольника). В классе Figure есть два
абстрактных метода - square (для подсчета площади) и perimeter (для периметра). Так как
для произвольной фигуры формул для площади и для периметра не существует, то эти
методы объявлены в классе Figure и переопределены в производных классах (с ключевым
словом override). Далее в классе Test мы проводим испытание - заводим две переменные
типа ссылка на базовый класс Figure, ниже в эти ссылки мы записываем созданные
экземпляры производных классов Triangle и Rectangle. Обратите внимание, что ссылку на
абстрактный класс мы создать можем, а экземпляр - нет. Далее мы выводим на экран
периметр и площадь для наших фигур.

30/69
Основы C#.

Урок 20. Запрещение наследования или модификатор


sealed
На прошлом уроке мы рассмотрели абстрактные классы. Их основное назначение - быть
предками для других классов. На другом конце иерархии стоят так называемые sealed-
классы. От них нельзя производить другие классы. Синтаксис для них такой же, как и для
обычных классов, за исключением ключевого слова sealed. Вот пример:
...
sealed class MyClass
{
//Методы и переменные класса
int x;
...
}
class MyClass2: MyClass //Ошибка!
{
...
}
...

Как вы видите, от sealed-класса MyClass нельзя делать классы-потомки.

31/69
Основы C#.

Урок 21. Статические переменные


Обычно переменные класса принадлежат конкретному экземпляру класса. Для обращения
к таким переменным мы пишем что-то вроде
MyClass z;
k=z.data;
Здесь переменная data принадлежит классу MyClass. Для каждого экземпляра класса она
своя.
Наряду с обычными переменными существуют и статические переменные класса. Их
основное отличие от обычных переменных в том, что они относятся к классу целиком. Т.
е. на все экземпляры класса у нас есть только одна переменная. Получить доступ к такой
переменой можно только для самого класса без создания его экземпляра. Через
конкретный же экземпляр класса доступа к такой переменной нет. Т. е. использовать
такую статическую переменную можно только для самого класса без создания
экземпляра.
Для объявления переменной статический надо использовать ключевое слово static. Вот
пример:
class MyClass
{
//Статическая переменная
public static int data;
//Конструктор
public MyClass()
{
data++;
}
}
class Test
{
public static void Main()
{
MyClass a=new MyClass();
MyClass b=new MyClass();
System.Console.WriteLine(MyClass.data);
}
}

Указанный фрагмент выведет на экран, естественно, 2 (переменная data увеличится в


каждом из двух конструкторов).

32/69
Основы C#.

Урок 22. Статические методы класса


На предыдущем уроке мы рассмотрели статические переменные класса. При этом наша
переменная data была объявлена как public. Это не слишком хорошо - переменные класса
лучше прятать от посторонних глаз. Но, так как доступ к нашей переменной нам все-таки
нужен, то мы добавим в наш класс функции для чтения и записи переменной data. Вот
новый вариант нашего класса:
class MyClass
{
//Статическая переменная
static int data;
//Статический метод для чтения переменной
public static int GetData()
{
return data;
}
//Статический метод для записи переменной
public static void SetData(int newData)
{
data=newData;
}
//Конструктор
public MyClass()
{
data++;
}
}
class Test
{
public static void Main()
{
MyClass a=new MyClass();
MyClass b=new MyClass();
//Вызов статического метода GetData
System.Console.WriteLine(MyClass.GetData());
//Вызов статического метода SetData
MyClass.SetData(5);
System.Console.WriteLine(MyClass.GetData());
}
}

Указанный фрагмент выведет, разумеется, 2 и 5.


Обратите внимание, что к статическим переменным и методам мы всегда получаем доступ
через имя класса, а к нестатическим - через экземпляр класса. Но это не значит, что мы не
имеем доступа к статическим переменным класса из нестатических методов класса -
имеем, и даже у нас есть пример этому - конструктор нашего класса MyClass.

33/69
Основы C#.

Урок 23. Статические конструкторы


Статическими к классе могут быть и конструкторы. Если обычный конструктор
вызывается в момент создания экземпляра класса, то статический - в самом начале, т. е.
перед созданием первого экземпляра класса.
В статическом конструкторе можно задать начальные значения для статических
переменных класса. Вот пример:
class MyClass
{
public static int data;
//Статический конструктор
static MyClass()
{
data=10;
}
//Конструктор
public MyClass()
{
data++;
}
}
class Test
{
public static void Main()
{
MyClass a=new MyClass();
MyClass b=new MyClass();
System.Console.WriteLine(MyClass.data);
}
}
Указанный фрагмент выведет на экран 12. Действительно, сначала вызовется статический
конструктор, который присвоит значение 10 переменной data. Затем для каждого
созданного экземпляра класса MyClass вызовется обычный конструктор, к котором
переменная data увеличится каждый раз на 1.
Обратите внимание, что оба конструктора без параметра. Противоречия тут нет, так как
один из них - статический, а другой - нет.

34/69
Основы C#.

Урок 24. Закрытые конструкторы или классы без


экземпляров
В C# нет глобальных переменных, функций или констант. Все они обязательно должны
принадлежать некоторому классу. И если у вас имеется класс, содержащий в себе,
например, некоторые нужные вам константы, то экземпляры такого класса вам не нужны
(так как вы создали этот класс исключительно как хранилище). Чтобы запретить создание
экземпляров класса, вы объявляете конструктор в нем как закрытый (то есть с
модификатором private или вовсе без модификатора - так как отсутствие модификатора и
подразумевает private). Это означает, что создать экземпляр такого класса не удастся (что
нам и надо).
Вот пример такого класса:
namespace test
{
//Класс без экземпляра
class MyClass
{
//Закрытый конструктор
private MyClass()
{
}
public static int MyPhoneNumber=1239999;
}
class Test
{
public static void Main()
{
System.Console.WriteLine(MyClass.MyPhoneNumber);
}
}
}
Тут у нас класс MyClass имеет закрытый конструктор, так что создавать его экземпляры
мы не можем. Так, следующий код будет ошибочным:
class Test
{
public static void Main()
{
MyClass a=new MyClass(); //ошибка!!!
}
}

Использовать же статические переменные и константы такого класса мы можем (так, мы


выводим на экран константу MyPhoneNumber).
Разумеется, в таком классе может содержать и методы:
class MyClass
{
//Закрытый конструктор
private MyClass()
{
public static int MyPhoneNumber=1239999;
public static int SomeNumber=1;
public static int GetSomeNumber(){
SomeNumber++;
return SomeNumber;
}
}
class Test
{
public static void Main()

35/69
Основы C#.

{
System.Console.WriteLine(MyClass.GetSomeNumber()); //Выведется 2
System.Console.WriteLine(MyClass.GetSomeNumber()); //Выведется 3
}
}
Указанный фрагмент выведет 2 и 3.

36/69
Основы C#.

Урок 25. Передача параметров переменой длины


В функцию можно передавать заранее неизвестное число параметров. Например, функция
может складывать некоторое заранее неизвестное количество чисел и возвращать их
сумму.
Вот конкретный пример реализации такой функции:
using System;
class MyClass{
public static int Sum(params int[] args)
{
int res=0;
for(int i=0; i<args.GetLength(0); i++)
{
res+=args[i];
}
return res;
}
}
namespace test
{
class Test
{
public static void Main()
{
System.Console.WriteLine(MyClass.Sum(1, 3));
System.Console.WriteLine(MyClass.Sum(12));
System.Console.WriteLine(MyClass.Sum(-1, 9, 2));
}
}
}
Здесь в классе MyClass мы объявляем статическую функцию Sum, в которую мы можем
передавать переменное число параметров. Для этого в круглых скобках после имени
функции мы пишем конструкцию
...
public static int Sum(params int[] args)
...

Синтаксис у нее такой - сначала пишем ключевое слово params, затем - тип параметров
(int в данном случае), после которого ставим пустые квадратные скобки и в конце этой
конструкции пишем произвольное имя параметра (в нашем примере он назван args).
В классе test мы испытываем наш класс MyClass, а именно, вызываем функцию Sum с
разным числом параметров. Все работает как надо и наша программа покажет на экране 4,
12 и 10.

37/69
Основы C#.

Урок 26. Строки (класс System.String)


Строки в C# - это экземпляры класса System.String. Вообще говоря в C# есть тип string, но
класс System.String является более продвинутым, так что его использование часто
оказывается более оправданным и простым. Этот класс имеет множество методов и
свойств, некоторые из которых перечислены ниже:
Свойство Length. Возвращает длину строки. Пример использования:
String s="qqq";
int k=s.Length; //В k запишется 3
Compare. Статический метод, сравнивающий две строки. Возвращает 0, есть строки
равны, отрицательное значение, если первая строка меньше второй и положительное
значение, если первая строки больше второй (больше и меньше в алфавитном смысле,
разумеется). Пример использования:
namespace test
{
class Test
{

public static void Main()


{
String s1="arbour", s2="ace", s3="azote";
System.Console.WriteLine(String.Compare(s1, s1)); //Выдаст
0, т. к. "arbour" равно "arbour".
System.Console.WriteLine(String.Compare(s1, s2)); //Выдаст
-1, т. к. "arbour" меньше "ace".
System.Console.WriteLine(String.Compare(s1, s3)); //Выдаст
1, т. к. "arbour" больше "azote".
}
}
}

Equals. Метод, возвращает true, если строки равны, false - если не равны. Может быть как
статическим, так и не статическим. Пример использования:
String s1="qqq", s2="www";
System.Console.WriteLine(String.Equals(s1, s2).ToString());
//Статический метод
System.Console.WriteLine(System.Console.WriteLine(String.Eq
uals(s1, s2).ToString());
//Не статический метод
System.Console.WriteLine(s1.Equals(s2).ToString());.ToString());
Метод Substring. Позволяет извлечь из строки подстроку. Пример использования:
String s1="abcdefg", s2;
s2=s1.Substring(3, 2);
System.Console.WriteLine(s2); //Напечатается "de"

Параметры тут такие: первый - с какого места извлекаем (нумерация с нуля) и второй -
сколько символов извлекаем.
Метод Insert. Вставляет в строку другую строку. Пример использования:
String s1="abcdefg", s2;
s2=s1.Insert(1, "xyz");
System.Console.WriteLine(s2); //Напечатается "axyzbcdefg"
Первый параметр тут - это куда вставляем (нумерация, как всегда, с нуля), второй - что за
строчку вставляем.
Метод IndexOf. Позволяет найти в строке подстроку. Пример использования:
String s1="abcabcab", s2="bc", s3="zzz";
System.Console.WriteLine(s1.IndexOf(s2)); //Напечатается 1

38/69
Основы C#.

System.Console.WriteLine(s1.IndexOf(s3)); //Напечатается -1
Этот метод возвращает номер позиции, на котором в строке находится передаваемая в
качестве параметра подстрока. Если такой подстроки нет, то возвращается -1.
Метод Replace. Производит замену в строке. Пример использования:
String s1="abcabcab", s2="bc", s3;
s3=s1.Replace(s2, "q");
System.Console.WriteLine(s3); //Напечатается aqaqab

Методы EndWith и StartsWith. Проверяют, не завершается ли или не начинается ли строка


с или на заданную строку соответственно. Пример использования:
String s1="arbour";
if(s1.StartsWith("ar"))
System.Console.WriteLine("Строка начинается на \"ar\"");
else
System.Console.WriteLine("Строка не начинается на \"ar\"");
Методы ToUpper и ToLower переводят строку в верхний или нижний регистр
соответственно. Пример использования:
String s1="aRbRur";
s1=s1.ToLower();
Методы Trim, TrimEnds и TrimStart. Удаляют пробельные символы из начала и конца
строки (Trim), только с конца строки (TrimEnds) и только с начала строки (TrimStart).
Пример использования:
String s1=" ar brur ";
System.Console.Write(s1.Trim());
Заметьте, что при изменении строки фактически старый экземпляр класса System.String
уничтожается, и создается новый с там же именем и измененным содержанием. Это
означает, что при интенсивной работе со строками программа может работать не так
быстро, как хотелось бы. Если мы не хотим, что бы каждый раз создавался новый
экземпляр класса, то вместо класса System.String надо использовать класс StringBuilder,
который мы рассмотрим на следующем уроке.

39/69
Основы C#.

Урок 27. Строки (класс StringBuilder)


На этом уроке мы рассмотрим другой класс для работы со строками - класс StringBuilder.
Он принадлежит пространству имен System.Text
Этот класс работает быстрее, чем класс String, так как при изменении строки, созданной
как экземпляр класса String, у нас создается каждый раз новый экземпляр класса, а старый
уничтожается, при использовании же класса StringBuilder расходов на создание-
уничтожение экземпляра класса нет - мы работаем всегда с одним экземпляром.
Обратите внимание, что для этого класса мы не можем использовать простое
присваивание:
StringBuilder s="abc"; //Неправильно!

В этом случае мы должны действовать так:


StringBuilder s=new StringBuilder("abc"); //Правильно
У класса StringBuilder нет статических методов. Все его методы - динамические. Ниже
перечислены основные свойства и методы класса StringBuilder:
Свойство Length. Возвращает длину строки. Пример использования:
int k=s.Length;
Свойство только для чтения MaxCapacity. Дает максимальное количество символов,
которые можно записать в объект типа StringBuilder. Пример использования:
System.Console.WriteLine(s.MaxCapacity);
Метод Append. Прибавляет строку к существующей. Пример использования:
StringBuilder s1=new StringBuilder("Cogito ");
StringBuilder s2=new StringBuilder("ergo ");
s1.Append(s2);
s1.Append("sum");
System.Console.WriteLine(s1); //Напечатается "Cogito ergo sum"

Метод Equals. Служит для сравнения двух строк. Возвращает true или false. Пример
использования:
if(s1.Equals(s2))
System.Console.WriteLine("Строки равны");
else
System.Console.WriteLine("Строки не равны");

Метод Insert. Вставляет символы в заданную позицию (Нумерация идет с нуля). Пример
использования:
StringBuilder s1=new StringBuilder("abcde");
s1.Insert(2, "xyz");
System.Console.WriteLine(s1); //Напечатается "abxyzcde"
Метод Remove. Удаляет символы из строки. Пример использования:
StringBuilder s1=new StringBuilder("abcde");
s1.Remove(1, 2);
System.Console.WriteLine(s1); //Напечатается "ade"

Первый параметр у Remove - это с какой позиции удаляем (нумерация с нуля), второй -
сколько символов удаляем.
Метод Replace. Заменяет символы. Пример использования:
StringBuilder s=new StringBuilder("abcdeabcde");
s.Replace("abc", "ZZZ");
System.Console.WriteLine(s); //Напечатается "ZZZdeZZZde"

40/69
Основы C#.

Урок 28. Передача параметров по ссылке и по значению


(ref и out)
Если мы передаем в некоторую функцию параметр, то функция по умолчанию работает с
копией параметра, а не с оригиналом. Это называется передача по значению. При этом
при изменении передаваемого параметра внутри функции значение соответствующей
переменной вне функции не изменится. Иногда же нам надо, что бы функция
действительно изменяла передаваемые в нее параметры. Для этого мы должны передавать
их по ссылке. Для этого мы используем ключевое слово ref. Вот пример:
class Test
{
static void SomeFunction1(int a)
{
a=12; //Изменяем копию параметра функции
}
static void SomeFunction2(ref int a)
{
a=55; //Изменяем оригинал параметра функции
}
public static void Main()
{
int z=23;
SomeFunction1(z);
Console.WriteLine(z); //Напечатается 23
SomeFunction2(ref z);
Console.WriteLine(z); //Напечатается 55
}
}
Обратите внимание, что при вызове функции SomeFunction2 мы опять используем
ключевое слово ref (как и при объявлении этой функции):
...
SomeFunction2(ref z);
...

Параметр out аналогичен параметру ref, только при его использовании в функцию можно
передавать неинициализированные переменные (т. е. переменные с незаданными
начальными значениями). Вот пример:
class Test
{
static void SomeFunction2(ref int a)
{
a=55;
}
static void SomeFunction3(out int a)
{
a=66;
}
public static void Main()
{
int z=0; //Переменную z надо обязательно инициализировать
SomeFunction2(ref z);
Console.WriteLine(z); //Напечатается 55
int y; //Переменную y можно не инициализировать
SomeFunction3(out y);
Console.WriteLine(y); //Напечатается 66
}
}
При вызове такой функции обязательно использование ключевого слова out.

41/69
Основы C#.

Урок 29. Пример передачи по ссылке


На этом уроке мы напишем функцию для решения квадратного уравнения. Эта функция
будет передавать корни уравнения через ссылку - т. е. через свои два параметра. Всего же
параметров будет пять - остальные три - это коэффициенты a, b, и c. Тип у функции будет
int - она будет возвращать фактическое число корней (т. е. 0, 1 или 2).
Вот текст:
using System;
namespace test
{
class Test
{
static int GetSolution(out double x1, out double x2, double a,
double b, double c)
{
double d=b*b-4*a*c; //Дискриминант
x1=x2=0;
//Два разных корня
if(d>0)
{
x1=(-b+Math.Sqrt(d))/(2*a);
x2=(-b-Math.Sqrt(d))/(2*a);
return 2;
}
//Два совпадающих корня
if(d==0)
{
x1=-b/(2*a);
x2=-b/(2*a);
return 1;
}
//Корней нет
return 0;
}
public static void Main()
{
double x1, x2;
int n = GetSolution(out x1, out x2, 1, -5, 6);
switch(n)
{
case 1:
Console.WriteLine("Два совпадающих корня x1={0},
x2={1}", x1, x2);
break;
case 2:
Console.WriteLine("Два разных корня x1={0},
x2={1}", x1, x2);
break;
case 0:
Console.WriteLine("Корней нет");
break;
}
}
}
}
Запускаем программу. Программа должна выдать на экран "Два разных корня x1=3,
x2=2".

42/69
Основы C#.

Урок 30. Перегрузка


При перегрузке мы имеем в одной области видимости два или более одноименных метода.
Для того, чтобы при компиляции не было ошибок, эти методы должны различаться или
типом параметров, или их количеством (или и тем и другим).
Вот пример:
using System;
namespace test
{
class SomeClass{
//Первый метод SomeFunc.
public static int SomeFunc()
{
return 0;
}
//Второй метод SomeFunc.
public static int SomeFunc(int k)
{
return k*k;
}
//Третий метод SomeFunc.
public static float SomeFunc(float k)
{
return k;
}
}
class Test
{
static void Main(string[] args)
{
Console.WriteLine(SomeClass.SomeFunc());
Console.WriteLine(SomeClass.SomeFunc(10));
Console.WriteLine(SomeClass.SomeFunc((float)10));
}
}
}

Результатом выполнения программы будет 0 100 10.


Тут в классе SomeClass мы имеет три метода с одним названием - SomeFunc. Для
простоты мы сделали все методы статическими, но это не важно. Наши методы
различаются количеством параметров и типом. В тестовом классе Test мы вызываем эти
методы. Обратите внимание на подсказку редактора - при написании метода SomeFunc
возникает желтенькое окошко в котором написано "1 of 3" (т. е. первый метод из трех):

Если нажимать стрелочки вверх-вниз на клавиатуре, то появятся подсказки и по другим


двум методам.
Обратите внимание на два момента. Во-первых, методы не могут различаться только
типом возвращаемого значения - они должны различаться именно по параметрам.
Например, такой метод добавить в класс SomeClass нельзя:
public static bool SomeFunc()
{
return true;
}
Во-вторых, в вызове третьего метода мы использовали приведение типов:
...

43/69
Основы C#.

Console.WriteLine(SomeClass.SomeFunc((float)10));
...

Если бы мы вызвали метод без слова float, то вызвался бы перегруженный вариант для
целых чисел.

44/69
Основы C#.

Урок 31. Функции класса System.Array


Массивы в C# основаны на классе System.Array. У этого класса, как и у любого другого,
есть разные полезные методы.
Вот пример:
using System;
namespace test
{
class Test
{
static void Main(string[] args)
{
//Объявление массива.
int [] num = {4, -5, 2, 0, 23};
//Выводим массив.
foreach (int i in num)
{
Console.WriteLine(i.ToString());
}
//Переворачиваем массив.
Console.WriteLine("Перевернутый массив");
Array.Reverse(num);
foreach (int i in num)
{
Console.WriteLine(i.ToString());
}
//Сортируем массив.
Array.Sort(num);
Console.WriteLine("Отсортированный массив");
foreach (int i in num)
{
Console.WriteLine(i.ToString());
}
//Обнуляем массив.
Array.Clear(num, 0, 5);
Console.WriteLine("Обнуленный массив");
foreach (int i in num)
{
Console.WriteLine(i.ToString());
}
}
}
}

Тут мы используем статические методы класса Array для сортировки, переворачивания и


очистки массива. Разумеется, эти методы не единственные.
В классе Array есть, например, еще встроенный статический метод IndexOf
предназначенный для поиска элемента в массиве. Вот пример его использования:
int k=-5;
Console.WriteLine("Число {0} находится на {1} месте.", k,
Array.IndexOf(num, k));
Этот метод возвращает индекс искомого элемента (нумерация с нуля). Если такого
элемента нет, то возвращается -1.
Обратите внимание, что эти методы действуют для встроенных типов (в нашем примере
массив был типа int). Для пользовательских типов данных их применение тоже возможно,
но для этого на приложить некоторые дополнительные усилия.
Как видите, с массивами в C# можно автоматически делать много чего полезного. В Visual
C++, например, такого удобства не было.

45/69
Основы C#.

Урок 32. Делегаты


Сначала пара слов о том, что такое делегаты. В языке программирования могут быть
переменные, содержащие значения целого или вещественного типов. С такими типами все
вы хорошо знакомы. Так вот, в первом приближении делегаты - это тоже некоторый тип
данных. Что же за тип может хранится в делегатах? В делегатах могут хранится функции
(вернее указатели на функции). Т. е. переменной типа делегат можно сначала присвоить
одну функцию, поработать с ней, а затем присвоить другую функцию. Это все в первом
приближении. Теперь несколько подробностей. Во-первых, делегаты бывают разных
типов. Тип делегата определяется типов и количеством параметров и типом
возвращаемого значения. Это значит, что если, например, делегат типа void и у него
только один параметр типа int, то в такой делегат мы можем записать только функцию
типа void и с одним единственным параметром типа int. Во-вторых, делегаты, в отличие
от указателей на функции языка C++, представляют из себя безопасные типы данных.
Вот пример делегата:
using System;
namespace test
{
//Объявление делегата.
delegate float MyFunc(float x);
//Класс для тестирования делегата.
class Test
{
//Первая функция.
static float f1(float x)
{
return x;
}
//Вторая функция.
static float f2(float x)
{
return x*x;
}
//Третья функция.
static float f3(float x)
{
return (float)Math.Sqrt(x);
}
//Метод Main.
static void Main()
{
//Создаем экземпляр делегата.
MyFunc f = new MyFunc(f1);
int a; //Ответ пользователя.
Console.WriteLine
("Выберите
функцию:\n1.f1=x.\n2.f2=x*x.\n3.f3=sqrt(x).");
//Преобразуем ответ пользователя к типу Int32
a=Int32.Parse(Console.ReadLine());
switch(a)
{
case 1:
//Записываем в делегат 1-ю функцию.
f=new MyFunc(f1);
break;
case 2:
//Записываем в делегат 2-ю функцию.
f=new MyFunc(f2);
break;
case 3:
//Записываем в делегат 3-ю функцию.
f=new MyFunc(f3);

46/69
Основы C#.

break;
}
//Печатаем результат.
int x=4;
Console.WriteLine("f({0})={1}", x, f(x));
}
}
}

В этой программе мы сначала объявляем делегат:


...
delegate float MyFunc(float x);
...
Синтаксис объявления делегата такой - сначала пишем ключевое слово delegate, затем -
тип возвращаемого значения (float в нашем примере), потом - произвольное имя делегата
(у нас это MyFunc), после которого в круглых скобках перечисляем параметры (у нас
только один параметр типа float). Объявленный делегат появится на вкладке ClassView (со
специальным значком для делегатов):

Далее мы в нашем тестовом классе объявляем несколько методов - f1, f2 и f3. Все эти
методы имеют тип float и один параметр типа float (как и у делегата MyFunc). Функции
делают свой подсчет по-разному - первая просто возвращает параметр, вторая - квадрат
параметра, третья - корень из параметра.
Далее мы в методе Main создаем экземпляр нашего делегата и в зависимости от ответа
пользователя записываем в него f1, f2 или f3. Потом в строке
...
Console.WriteLine("f({0})={1}", x, f(x));
...
мы выводим значение делегата при некотором x (равном 4 в нашем примере). Вернее
сказать, мы выводим не значение делегата, а значение функции, которую мы записали в
делегат.
В зависимости от ответа пользователя в делегат запишется тот или иной вариант функции
и программа выведет сам x (4), x умножить на x (16) или корень из x (2):

47/69
Основы C#.

Урок 33. События


На этом занятии мы с вами рассмотрим создание событий в C#. С помощью событий
класс извещает, что в нем что-то произошло. События часто создаются в классах
используемых для создания пользовательского интерфейса - например у кнопки есть
событие, наступающее при щелчке на этой кнопке. События также могут присутствовать
и в классах, не предназначенных для создания пользовательского интерфейса - например,
они могут быть в классах консольного приложения. Именно для такого класса мы и
рассмотрим создание события.
События в C# основаны на делегатах. Это означает, что событие имеет тип определенного
делегата. Вот пример класса с событием в нем:
using System;
namespace test
{
//Объявление делегата.
delegate void EventHandler();
//Объявление класса с событием.
class MyEvent
{
//Объвление события на основе делегата.
public event EventHandler f;
//Объвление метода, в котором вызывается событие.
public void func()
{
f();
}
}
//Класс для тестирования события класса MyEvent.
class Test
{
//Обработчик для события.
public static void z()
{
Console.WriteLine("Вызов обработчика");
}
//Метод main.
static void Main(string[] args)
{
//Создаем экземпляр класса с событием.
MyEvent w=new MyEvent();
//Добавление обработчика события.
w.f+=new EventHandler(z);
//Вызов метода, в котором вызывается событие.
w.func();
}
}
}
Сначала мы в строке
...
delegate void EventHandler();
...

объявляем делегат. Параметров у него нет (хотя могут и быть), тип - void.
Затем мы объявляем класс MyEvent, внутри которого объявляем событие f (имя
произвольное):
...
public event EventHandler f;
...

48/69
Основы C#.

Синтаксис тут такой - модификатор доступа (у нас public), затем ключевое слово event,
потом имя делегата, на основании которого мы создаем наше событие (у нас это
EventHandler) и, наконец, имя произвольное событие (f). Обратите внимание, что событие
появится на вкладке ClassView:

Далее в нашем классе с событиями мы в некотором методе это событие вызываем:


...
public void func()
{
f();
}
...
Класс с событием создан. Далее мы пишем класс Test для тестирования события. В этом
классе мы создаем обработчик z для события (имя обработчика произвольное). Этот метод
мы объявляем как статический.
Далее мы объявляем экземпляр нашего класса MyEvent (того самого, в котором мы
объявили событие):
...
MyEvent w=new MyEvent();
...

Остается теперь указать, что за обработчик будет у события f класса MyEvent. Это мы
делаем в строке
...
w.f+=new EventHandler(z);
...
Этой строчкой мы указываем, что событие f будет обрабатывать метод z класса Test.
Если мы запустим наше программу, то на вызовется обработчик для события (т. е.
выведется надпись "Вызов обработчика"):

Обратите внимание, что обработчик для события мы пишем в тестовом классе (т. е. в
классе Test, в котором мы объявили экземпляр нашего класса с событиями). Так мы
делаем всегда, и не только на платформе .NET.

49/69
Основы C#.

Урок 34. Пример класса с событием


На этом уроке мы рассмотрим еще один пример класса с событием. Класс наш будет
представлять из себя игральную кость, в классе будет единственный метод rnd,
возвращающий случайное число от 1 до 6. Если этот метод возвратит 6, то сгенерируется
событие max.
namespace test
{
//Объявление делегата.
public delegate void EventHandler();
//Класс "Игральная кость".
class MyDie
{
Random r;
//Объвление события на основе делегата.
public event EventHandler max;
//Конструктор.
public MyDie()
{
r=new Random();
}
//Объявление метода, в котором вызывается событие.
public int rnd()
{
//Случаное число от 1 до 6.
int res = r.Next(6)+1;
if(res==6)
{
//Вызываем событие.
max();
}
return res;
}
}
//Класс для тестирования события.
class Test
{
//Обработчик для события.
public static void z()
{
Console.WriteLine("Вызов обработчика");
}
//Метод main.
static void Main(string[] args)
{
//Создаем экземпляр события.
MyDie w=new MyDie();
//Добавление обработчика события.
w.max+=new EventHandler(z);
//Вызов метода, в котором вызывается событие.
for(int k=0; k<10; k++)
{
Console.WriteLine("{0}", w.rnd());
}
}
}
}

При запуске нашей программы к классе Test мы создаем новый экземпляр класса MyDie
игральной кости, приписываем к событию max класса MyDie обработчик z, и
подкидываем кость 10 раз. Если выпадет шестерка, то возникнет событие max и
выполнится обработчик для него. Результат выполнения программы может быть,
например, таким:

50/69
Основы C#.

51/69
Основы C#.

Урок 35. Введение в атрибуты


Сначала несколько слов о том, что такое атрибуты. Итак, атрибут - это некоторая
дополнительная информация, которая может быть приписана к типам, полям, методам,
свойствам и некоторым другим конструкциям языка. Атрибуты помещаются в
исполняемый файл и могут оттуда при необходимости извлекаться.
Все атрибуты (в отличие, скажем, от атрибутов языка IDL) являются классами (потомками
класса System.Attribute). В отличие от атрибутов IDL набор атрибутов .NET открыт для
дополнения, т. е. вы можете определять собственные атрибуты и применять их к
вышеуказанным элементам вашего кода.
Атрибуты делятся на предопределенные (встроенные) и пользовательские (которые пишет
программист).
Встроенные атрибуты могут использоваться, например, при сериализации (сохранении в
поток) данных класса. Скажем, вам надо, чтобы у класса сохранялись не все данные - в
этом случае вы можете пометить те данные, которые не надо сохранять, специальным
атрибутом.
Еще пример применения атрибутов. Компоненты, которые вы располагаете на форме
(кнопки, метки и т. п.) имеют некоторый набор свойств (шрифт, местоположение,
видимость и т. п.). В IDE Visual Studio вы можете выбрать в окне Properties один из двух
способов расположения этих свойств - по алфавиту или по категориям. Так вот, в какую
категорию попадет то или иное свойство, определяется специальным встроенным
атрибутом.
Атрибуты в C# заключаются в квадратные скобки.

52/69
Основы C#.

Урок 36. Пример пользовательского атрибута.


Вот пример определения и использования пользовательского атрибута:
using System;
namespace test
{
//Объявление атрибута.
[AttributeUsage(AttributeTargets.All, Inherited = false,
AllowMultiple = true)]
class TestAttribute: System.Attribute
{
//Поле атрибута.
private string name;
//Конструктор атрибута.
public TestAttribute(string name)
{
this.name = name;
}
//Свойство только для чтения.
public virtual string Name
{
get
{
return name;
}
}
}
//Конец объявления атрибута.
//Применение атрибута к классу.
[TestAttribute("Igor Alexeev")]
class Test
{
static void Main()
{
GetAttribute(typeof(Test));
}
public static void GetAttribute(Type t)
{
TestAttribute att=
(TestAttribute) Attribute.GetCustomAttribute(t,
typeof(TestAttribute));
Console.WriteLine("{0}", att.Name);
}
}
}
Как видно, атрибут TestAttribute является потомком класса System.Attribute (как всегда).
Перед определением нового атрибута мы видим строку
...
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple
= true)]
...
Этой строкой определяется область применения нашего атрибута - первый параметр
(AttributeTargets.All) говорит о том, что мы сможем применять наш атрибут TestAttribute к
чему угодно, второй (Inherited=false) - что атрибут не будет наследоваться (т. е. если мы
применим атрибут TestAttribute к некоторому классу, то у потомков этого класса атрибута
TestAttribute не будет), третий (AllowMultiple = true) - что атрибут к каждому элементу
может применяться только один раз (заметим в скобках, что для определения области
применения нашего пользовательского атрибута мы используем другой атрибут -
AttributeUsage).

53/69
Основы C#.

Далее мы определяем для нашего атрибута внутреннюю переменную name типа string и
конструктор с параметром типа string. В конструкторе мы записываем значение в
переменную name. Еще чуть ниже мы пишем в классе атрибута свойство только для
чтения:
...
public virtual string Name
{
get
{
return name;
}
}
...
После создания класса атрибута мы применяем его к другому классу Test. Для этого мы
должны создать экземпляр атрибута TestAttribute непосредственно перед классом Test:
...
[TestAttribute("Igor Alexeev")]
class Test
{
...

Делаем мы это в квадратных скобках. При этом у нас вызывается конструктор с


параметром атрибута.
В классе Test мы в методе GetAttribute просто выводим на консоль значение свойства
name атрибута. Для этого мы воспользовались статическим методом GetCustomAttribute
класса Attribute. Этот метод принимает в качестве параметров тип, к которому атрибут
применяется (т. е. класс Test в нашем случае) и собственно атрибут, который мы
применяем (у нас это TestAttribute). Возвращает же он экземпляр атрибута, который мы и
используем для получения значения свойства name.
Метод GetAttribute мы вызываем в конструкторе класса Test.
Результатом выполнения нашей программы будет вывод на консоль слов "Igor Alexeev".

54/69
Основы C#.

Урок 37. Параметры командной строки


Параметры командной строки передаются, естественно, в методе Main. В отличие от
некоторых языков программирования (C/C++) параметр только один - это массив
передаваемых параметров. Аналога первого параметра языка C/C++ (количества
параметров) нет. Передаются параметры в виде массива строк.
Вот пример программы, выводящий на экран все передаваемые в нее параметры
командной строки и их количество:
using System;
namespace test
{
class Test
{
//Передаем в Main параметры командной строки.
static void Main(string[] args)
{
//Выводим количество параметров.
Console.WriteLine(args.GetLength(0));
//Выводим каждый параметр.
foreach (string s in args)
{
Console.WriteLine(s);
}
}
}
}
Обратите внимание, что в сам exe-файл не считается параметром (как в C/C++).

55/69
Основы C#.

Урок 38. Метод Main в отдельном классе


До этого мы в большинстве случае помещали метод Main в тот же класс, который
испытывали:
using System;
namespace test
{
class Test
{
//... (поля, методы, ... класса)
static void Main()
{
//...
}
}
}

Но для больших, настоявших программ это не самый лучший способ. Гораздо лучше
вынести метод Main в отдельный класс:
using System;
namespace test
{
class Test
{
//... (поля, методы, ... класса Test)
}
//Класс приложения для вызова метода Main.
class App
{
static void Main()
{
//Создаем экземпляр класса Test.
Test c = new Test();
//...
}
}
}

Вообще говоря в программе у вас, как правило, будет много классов. И лучше каждый из
них хранить в отельном файле (с расширением *.cs).

56/69
Основы C#.

Урок 39. Форматированный вывод


Для вывода на консоль мы использовали следующую конструкцию:
...
int x=23, y=-4;
...
Console.WriteLine("x={0}, y={1}", x, y);
...
Здесь мы используем внутри кавычек подстановочные знаки {0}, {1} и т. д. (нумерация в
них идет с нуля). Переменные при этом выводятся в формате по умолчанию. Для вывода в
определенном формате надо использовать подстановочные знаки с параметрами. Вот
некоторые их них:
• d - десятичный формат. Позволяет задать общее количество знаков (при
необходимости число дополняется слева нулями).
• f - формат с фиксированной точностью. Позволяет задать количество знаков после
запятой.
• x - шестнадцатеричный формат.
• c - денежный формат (добавляет знак доллара и показывает два знака после
запятой).
• e - вывод числа в экспоненциальной форме.
А вот пример их использования:
...
int a=38;
//Выведется 0038
Console.WriteLine("a={0:d4}", a);

double pi=3.1415926;
//Выведется 3.14
Console.WriteLine("pi={0:f2}", pi);

int b=255;
//Выведется FF.
Console.WriteLine("b={0:X}", b);

int c=255;
//Выведется ff.
Console.WriteLine("c={0:x}", c);

double d=1003.214;
//Выведется $1, 003.14 в английской версии Windows и
//1 003,14 р. в русской.
Console.WriteLine("d={0:c}", d);

double e=213.1;
//Выведется 2.131000e+002
Console.WriteLine("e={0:e}", e);
...
Параметры подстановочных знаков можно использовать как строчные, таки и прописные -
это все равно. Исключение - вывод числа в шестнадцатеричном виде (при использовании
h цифры a, ..., f будут строчными, при использовании H - прописными).

57/69
Основы C#.

Урок 40. Класс System.Object


Класс System.Object является предком для всех классов. Это происходит неявным образом
- при объявлении класса мы System.Object не указываем в качестве класса предка. Хотя
это и можно сделать (результат будет одним и тем же):
class Test:System.Object
{
...
}
То, что все классы являются потомками класса System.Object, позволяет нам использовать
методы последнего.
Еще одно из следствий этого - это возможность записать в переменную типа System.Object
экземпляр любого класса:
//Некоторый класс.
class Test
{
public string s;
}
class App
{
static void Main()
{
Object ob;
//Записываем в ob экземпляр класса-потомка Test.
ob=new Test();
//Приводим ob к типу Test.
((Test)ob).s="some string";
//Выводим значение поля s.
Console.WriteLine(((Test)ob).s);
}
}
Указанный фрагмент выведет, естественно, строку "some string".

58/69
Основы C#.

Урок 41. Методы класса System.Object


В классе System.Object имеются следующие методы:
• Equals - виртуальный метод, возвращающий True, если два объекта расположены в
одном месте памяти (второй объект передается в качестве параметра).
• GetHashCode - виртуальный метод, возвращает некоторое целое число (хэш-код),
однозначно идентифицирующее экземпляр класса.
• GetType - возвращает объект типа Type, описывающий соответствующий тип.
• ToString - виртуальный метод, по умолчанию возвращает строку, представляющую
полное имя типа объекта.
Вот пример использования этих методов:
Object ob1, ob2;
ob1=new System.Object();
ob2=ob1;
//Выведется True.
Console.WriteLine(ob1.Equals(ob2));

ob2=new System.Object();
//Выведется False.
Console.WriteLine(ob1.Equals(ob2));

//Выведется некотрое число.


Console.WriteLine(ob1.GetHashCode());

//Выведется System.Object.
Console.WriteLine(ob1.GetType());

//Выведется System.Object.
Console.WriteLine(ob1.ToString());
Виртуальные методы класса System.Object часто переписывают в классах-потомках.

59/69
Основы C#.

Урок 42. Переопределяем методы класса System.Object


Часть методов класса System.Object, рассмотренных на прошлом уроке, были
виртуальными. Это значит, что мы можем переписать их в классе-потомке. Давайте для
примера перепишем в нашем классе Vector метод ToString так, чтобы он возвращал не имя
класса, а координаты вектора в красивом виде. Это будет выглядеть вот так:
class Vector
{
public float x, y; //Координаты.
//Переопределяем метод ToString.
public override string ToString()
{
return "x = " + x +", y = " + y;
}
}
//Тестовый класс.
class App
{
static void Main()
{
Vector v=new Vector();
v.x=2;
v.y=3;
//Выводим координаты вектора.
Console.WriteLine(v.ToString());
}
}
А в следующем примере мы переопределим виртуальный метод Equals таким образом,
чтобы считать равными два вектора с одинаковой длиной (вспомним теорему Пифагора):
class Vector
{
...
//Переопределяем метод Equals.
public override bool Equals(object ob)
{
Vector t=(Vector)ob;
return (t.x*t.x+t.y*t.y)==(this.x*this.x+this.y*this.y);
}
}
//Тестовый класс.
class App
{
static void Main()
{
Vector v1=new Vector();
v1.x=2;
v1.y=3;
Vector v2=new Vector();
v2.x=-3;
v2.y=2;
Console.WriteLine(v1.Equals(v2));
}
}

В тестовом классе мы проверяем действие переопределенного метода для равных по


длине векторов (2, 3) и (-3, 2). Указанный фрагмент выведет True.

60/69
Основы C#.

Урок 43. Константы


Константы удобны использовать для величин, которые в программе не меняются.
Использование констант позволяет изменить некоторую величину везде в программе за
пару секунд - легче изменить значение в одном месте, чем по всей программе.
Константа определяется ключевым словом const. Вот пример:
class MyClass
{
//Объявление константы.
public const int SomeValue=20;
...
}
Обратите внимание, что константы, как и все в C#, определяется в классе.
Часто в программе заводят некоторый вспомогательный класс, главное предназначение
которого - это хранение в одном месте всех констант программы. Например, такой класс
может выглядеть так:
abstract class Constants
{
public const int SomeValue1=20;
public const int SomeValue2=100;
public const double SomeValue3=0.35;
}
Мы этот класс предопределили как абстрактный (см. урок 19) для того, чтобы нельзя было
создавать экземпляры этого класса. Он используется у нас только для хранения констант.
Использовать этот класс можно так:
class App
{
static void Main()
{
double z;
z=Constants.SomeValue2 * Constants.SomeValue3;
Console.WriteLine("z={0}", z); //Выведется 35.
}
}

61/69
Основы C#.

Урок 44. Модификаторы доступа


Члены класса (поля, методы и т. п.) могут иметь разные модификаторы доступа. Вот они:
Модификатор доступа Описание
public Доступность откуда угодно.
private Доступность только из этого же самого класса (по умолчанию).
protected Доступность только из этого же самого класса или его потомков.
internal Доступность из любого класса той же программы.
Доступность или из любого класса той же программы или из
protected internal этого же самого класса или его потомков (т. е. или как protected
или как internal)
Модификаторы доступа пишутся первыми - перед всеми другими ключевыми словами
(например, типом переменной). Модификатор static может стоять как перед
модификатором доступа, так и после:
...
static public int a;
public static int b;
...
В отличие от C/C++ модификаторы доступа пишутся для любого члена класса (как в Java):
class SomeClass
{
//Правильно.
public void f1()
{
//...
}
public void f2()
{
//...
}
...

62/69
Основы C#.

Урок 45. Ссылка на текущий экземпляр класса (this)


Ссылка на текущий экземпляр класса делается через ключевое слово this. Вот
традиционный пример:
class SomeClass
{
public int a;
SomeClass(int a)
{
this.a=a;
}
}
В этом примере у нас и переменная класса, и параметр метода названы одинаково - a. Для
уточнения, что мы используем именно переменную класса, а не параметр метода, и
служит слово this.
В C++ же для избежания конфликта имен обычно к переменной класса прибавлялся
префикс m_.
Еще одна возможность по использованию ключевого слова this - это вызов одного
конструктора из другого. Вот пример:
class SomeClass
{
public int a;
public int b;
//Конструктор с 2-я параметрами.
public SomeClass(int a, int b)
{
this.a=a;
this.b=b;
}
//Конструктор без параметров,
//вызывающий конструктор с 2-я параметрами.
public SomeClass():this(1, 1)
{
}

}
//Тестовый класс.
class App
{
static void Main()
{
SomeClass s=new SomeClass();
Console.WriteLine("a={0}, b={0}", s.a, s.b);
}
}
Здесь в классе SomeClass два конструктора. Второй конструктор (без параметров)
вызывает первый (передавая в него значения 1 и 1):
...
public SomeClass():this(1, 1)
...

Указанный фрагмент выведет, разумеется, a=1, b=1.

63/69
Основы C#.

Урок 46. Класс Environment


Класс Environment позволяет получить информацию об окружении программы (текущий
каталог, версия Windows и т. п.) через свои статические члены. Вот пример использования
этого класса:
string s;
//Текущая папка.
s=Environment.CurrentDirectory;
Console.WriteLine("Текущая папка: {0}.", s);
//Системная папка.
s=Environment.SystemDirectory;
Console.WriteLine("Системная папка: {0}.", s);
//Имя компьютера.
s=Environment.MachineName;
Console.WriteLine("Имя компьютера: {0}.", s);
//Oперационная система и ее версия.
OperatingSystem os=Environment.OSVersion;
Console.WriteLine("Операционная система: {0}, версия {1}.",
os.Platform, os.Version);
//Версия платформы .NET.
Version ver=Environment.Version;
Console.WriteLine("Версия платформы .NET: {0}.{1}.",
ver.Major, ver.Minor);
Указанный фрагмент выведет на экран имя папки, в которой запущено приложение, имя
системной папки Windows, NetBIOS имя компьютера, операционную систему и ее
версию, платформу .NET и ее версию:

64/69
Основы C#.

Урок 47. Работаем со специальными папками


Класс Environment, рассмотренный на прошлом уроке, позволяет выяснять и
местоположение различных специальных папок - SendTo, StartMenu, Program Files и др.
Это может оказаться очень удобным для, например, добавлении ярлыка на вашу
программу в папку Автозагрузки или на Рабочий Стол.
Специальные папки образуют перечисление Environment.SpecialFolder. Вот его некоторые
члены:
Специальная папка Описание
Cookies Папка с cookie
DesktopDirectory Рабочий стол
Favorites Избранное
History История (Internet Explorer)
Personal Папка "Мои документы"
ProgramFiles Папка "Program Files"
Recent Папка со списком последних открываемых документов
SendTo Папка SendTo
StartMenu Главное меню
Startup Папка автозагрузки
System Системная папка Windows
Обратите внимание, что некоторые из этих папок общие для всех пользователей, а
некоторые персональные для каждого пользователя.
Вот пример работы со специальными папками:
//Получаем папку SendTo.
Environment.SpecialFolder p=Environment.SpecialFolder.SendTo;
//Получаем путь к папке SendTo.
string s=Environment.GetFolderPath(p);
Console.WriteLine("Папка SendTo: {0}.", s);
//Получаем папку Startup.
p=Environment.SpecialFolder.Startup;
//Получаем путь к папке Startup.
s=Environment.GetFolderPath(p);
Console.WriteLine("Папка Startup: {0}.", s);
Тут мы сначала записываем в переменную типа Environment.SpecialFolder нужную папку,
затем извлекаем путь к нужной папке через метод GetFolderPath класса Environment.

65/69
Основы C#.

Урок 48. Получаем список всех дисков


Класс Environment позволяет получить и имена всех логических дисков компьютера.
Делается это через его статический метод GetLogicalDrives(). Вот пример:
string[] drives=Environment.GetLogicalDrives();
foreach(string s in drives)
{
//Выводим имена всех дисков.
Console.WriteLine(s);
}
А вот результат выполнения программы:

Метод GetLogicalDrives() возвращает строковый массив с именами всех дисков.

66/69
Основы C#.

Урок 49. Ввод/вывод в C# (System.IO)


Для операций ввода-вывода служит пространство имен System.IO.
Вот краткий обзор наиболее важных классов и перечислений из этого пространства имен:
• BinaryReader - позволяет читать из файла данные различных типов (целые,
вещественные, логические и т. п.)
• BinaryWriter - позволяет записывать в файл данные различных типов (целые,
вещественные, логические и т. п.)
• Directory - класс со статическими методами для работы с папками
• DirectoryInfo - класс для работы с некоторой папкой
• File - класс со статическими методами для работы с файлом
• FileInfo - класс для работы с некоторым файлом
• Path - класс для работы с файловыми путями
• Перечисление FileAttributes - атрибуты файла
• Перечисление FileMode - возможные способы открытия файла
• Перечисление FileAccess содержит константы, показывающие, открыт ли файл для
чтения, записи и др.
• FileSystemWatcher - класс для отслеживания изменений в файловой системе
• Перечисление NotifyFilters - параметры, по которым происходит отслеживание
изменений в файловой системе
• Перечисление WatcherChangeTypes - какие изменения отслеживаются в файловой
системе

67/69
Основы C#.

Урок 50. Классы для работы с папками и файлами


Для работы с файлами и папками в пространстве имен System.IO существуют следующие
классы:
Directory - содержит ряд статических методов для выполнения различных операций с
папками (создание, удаление, проверка существования, получение текущей папки и т. п.).
DirectoryInfo - аналогичен классу Directory, только его методы не статические - для
использования этого класса надо создать его экземпляр (параметром для конструктора
служит имя папки).
File - содержит ряд статических методов для выполнения различных операций с файлами
(создание, удаление, копирование и т. п.).
FileInfo - аналогичен классу File, только его методы не статические - для использования
этого класса надо создать его экземпляр (параметром для конструктора служит имя
файла).
Классы Directory и File являются непосредственными потомками класса Object, а классы
DirectoryInfo и FileInfo - потомками абстрактного класса FileSystemInfo.

68/69
Основы C#.

Урок 51. Класс Directory


Класс Directory предназначен для работы с папками. Этот класс содержит статические
методы для работы с папками (в отличие от класса DirectoryInfo, который содержит
аналогичные методы, применяемые для экземпляра класса).
Вот основные методы класса Directory (все они статические):
• CreateDirectory - создает папку с указанным именем.
• Exists - проверяет существование папки с указанным именем.
• Delete - удаляет папку с указанным именем.
• Move - перемещает или переименовывает папку с указанным именем.
А вот пример их употребления:
using System;
//Подключаем необходимое пространство имен.
using System.IO;
namespace constest
{
...
class Class1
{
...
static void Main(string[] args)
{
//Создаем папку.
Directory.CreateDirectory("C:\\temp");
//Проверка существования папки.
if(Directory.Exists("C:\\temp1"))
{
Console.WriteLine("Папка \"temp1\" существует");
}
else
{
Console.WriteLine("Папка \"temp1\" не существует");
}
if(Directory.Exists("C:\\temp"))
{
Console.WriteLine("Папка \"temp\" существует");
}
else
{
Console.WriteLine("Папка \"temp\" не существует");
}
//Перемещение папки.
Directory.Move( "C:\\temp", "C:\\temp2");
//Удаление папки.
Directory.Delete( "C:\\temp2");
}
}
}

Обратите внимание, что метод Move можно использовать не только для перемещения, но
и для переименования папки (что мы, фактически, в нашем примере и делаем). Для этого
папка, задаваемая первым параметром (т. е. та, которую перемещаем) должна находиться
на том же уровне, что и папка, задаваемая вторым параметром (т. е. куда перемещаем).

69/69