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

Классы в 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++,
мы можем задавать некоторое начальное значение прямо сразу после объявления переменной:

...
public int age=0;
...
Начальное значение задавать вовсе не обязательно - это видно по переменной name.

Перед переменными мы пишем ключевое слово public. Значение у него такое же, как и в C++ - а
именно это означает, что эта переменная (или функция) будет видна вне класса. Если мы не
напишем перед переменной никакого модификатора доступа, или напишем private, то пременая
не будет видна снаружи класса и ее смогут использовать только фунции этого же класса (т. е.
она будет для "внутреннего использования").

Далее в строчке

...
Worker wrk1 = new Worker();
...

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

Затем в строчках

...
wrk1.age=34;
wrk1.name="Иванов";
Console.WriteLine(wrk1.name+", "+wrk1.age);
...

мы используем наш класс. А именно присваиваем некоторые значения для возраста и имени и
выводим их потом на экран.

Пространства имен (namespaces) в C# (Csharp)


  Пространства имен (namespaces) предоставляют программистам возможность логичной
взаимосвязи классов и других типов. Само понятие namespaces скорее является логическим,
нежели физическим (например как файл или компонент). Каждый раз когда вы обьявляете класс
в C# - у вас есть возможность добавить его в пространство имен. Когда станет необходимо
расширять функциональную часть программы - вы с легкостью сможете добавить несколько
логически обьединенных классов в одно пространство имен.

  Для примера добавим структуру Book в пространство имен BookStore:

namespace BookStore
{
public struct Book
{
// Код структуры ...
}
}

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

  Каждое имя в пространистве имен состоит из названий тех пространств имен, в которое оно
входит и начинается из самого внешнего имени. Для примера: System.Windows.Forms. Такой
принцип именования может дать нам слишком длинные конструкции, которые неудобно
использовать в коде (System.Windows.Forms.Form) для этого в C# существует директива using,
которая пишется в самом начале кода имеет вид:

using System.Windows.Forms;

  Если мы подключили с помощью директивы using пространство имен


System.Windows.Forms.Form - то в коде уже можно напрямую обращаться к Form (без полной
записи). Важной особенностью пространств имен в C# является также и то, что они не зависят от
сборок. Можно создавать различные пространства в пределах одной сборки так само, как и
создавать несколько сборок в пределах одного пространства имен.

  Еще одним применением ключевого слова using в C# является назначение псевдонимов классам
и пространствам имен. Если у вас сложный проект и пространства имен имеют очень большую
вложенность - в таком случае пространству имен назначается псевдоним, который в общем виде
выглядит так:

using alias = NamespaceName;

   В заключение хочется сказать что программисты, которые переходят на C# из С++ очень часто
путают пространства имен с заголовочными файлами #include. Главное не путать эти понятия,
так как пространства имен это не файлы - а логическое понятие, которое обьединяет классы и
типы, которые имеют логические связи.

Конструкторы классов
Для начала кратко обсудим, что такое конструктор вообще. Конструктор - это функция (метод)
класса. Раз эта функция, то описываем мы ее почти точно так же, как и любую другую функцию
класса - пишем параметры в круглых скобках и т. п. Когда конструктор вызывается? В момент
создания переменной. При этом у класса может быть несколько конструкторов - но при этом они
должны различаться или типом параметров, или их количеством.

Откройте проект, созданный выше. Давайте добавим в наш класс 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 и "Вася". Строчкой
ниже эти значения выводятся на экран.

Наследование
Представьте себе, что у нас есть некоторый класс (быть может весьма большой), который почти
подходит для нашей конкретной задачи. Почти, но не совсем. Что-то (некоторые методы) в этом
классе надо изменить, что-то - добавить. Наследование как раз и служит для этого. При
наследовании мы объявляем наш класс потомком другого класса. И наш класс-потомок (или, как
его еще называют, дочерний класс) автоматически приобретает всё, что было в родительском
классе. Это мы делаем буквально парой строчек. Затем в дочерний класс мы можем что-нибудь
добавить (чего не было в классе родительском). Кроме того, в дочернем классе мы можем что-
нибудь из родительского класса переопределить - так что оно уже будет работать по-другому.

Давайте рассмотрим это все на примере. У нас будет класс 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:

...
class Boss : Worker{
...

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

...
public new void setAge(int age)
...

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


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

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

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


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

...
sealed class MyClass
{
//Методы и переменные класса
int x;
...
}
class MyClass2: MyClass //Ошибка!
{
...
}
...

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

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


производного
В переменной типа базового класса можно хранить и переменные производных класов.
Например, текст метода 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). И это удобно -
одним циклом вы сможете начислить всем зарплату и т. п.

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

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:

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

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

...
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;
}
...

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

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

В классе test мы испытываем наш перегруженный оператор. Все должно работать как и
положено, и результат сложения должен вывестись на консоль в виде "x=3, y=1".

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


Унарные операторы - это те, которые имеют только один операнд (параметр). Типичные примеры
унарных операторов - это унарный минус (меняющий знак на противоположный) и оператор
инкремента ++.
Рассмотрим перегрузку унарных операторов на примере рассмотренного класса 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)).

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

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, то
ошибки не будет.

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

Если в родительском классе некоторая функция объявлена как виртуальная, то в производном


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

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.

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

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());
}
}

Абстрактные классы
Методы класса могут быть объявлены как абстрактные. Это означает, что в этом классе нет
реализации этих методов. Абстрактные методы пишутся с модификатором 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
{
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.
Обратите внимание, что ссылку на абстрактный класс мы создать можем, а экземпляр - нет.
Далее мы выводим на экран периметр и площадь для наших фигур.

Статические переменные
Обычно переменные класса принадлежат конкретному экземпляру класса. Для обращения к
таким переменным мы пишем что-то вроде

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 увеличится в каждом из


двух конструкторов).

Статические методы класса


Мы рассмотрели статические переменные класса. При этом наша переменная 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.

Статические конструкторы
Статическими к классе могут быть и конструкторы. Если обычный конструктор вызывается в
момент создания экземпляра класса, то статический - в самом начале, т. е. перед созданием
первого экземпляря класса.

В статическом конструкторе можно задать начальные значения для статических переменных


класса. Вот пример:
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.

Обратите внимание, что оба конструктора без параметра. Противоречия тут нет, так как один из
них - статический, а другой - нет.

Закрытые конструкторы или классы без экземпляров


В 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()
{
System.Console.WriteLine(MyClass.GetSomeNumber()); //Выведется 2
System.Console.WriteLine(MyClass.GetSomeNumber()); //Выведется 3
}
}

Указанный фрагмент выведет 2 и 3.

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


В функцию можно передавать заранее неизвестное число параметров. Например, функция может
складывать некоторое заранее неизвестное количество чисел и возвращать их сумму.

Вот конкретный пример реализации такой функции:

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.