До сих пор мы работали с готовыми типами: числами, строками, списками. Класс позволяет объявить свой – такой, какого в языке нет, но который нужен вашей задаче.
Класс – это описание, объект – экземпляр
Класс похож на чертёж. На чертеже указано, из чего состоит изделие и что оно умеет, но сам чертёж никуда не поедет. Ездят машины, собранные по нему, – и каждая своя: разного цвета, с разным пробегом.
Класс – описание типа: какие данные у него есть и что с ними можно делать. Объект (экземпляр) – конкретная сущность в памяти, созданная по этому описанию.
Сколько бы объектов вы ни создали, описание типа существует в единственном экземпляре – одно на всю программу. Объекты хранят только свои значения полей. Сто машин по одному чертежу, а не сто чертежей.
Самый простой класс
public class Book
{
public string Title;
public string Author;
public int Year;
}
Внутри – поля: переменные, принадлежащие объекту. У каждого экземпляра они свои.
Создание объекта и работа с ним:
Book book = new Book();
book.Title = "Война и мир";
book.Author = "Толстой";
Console.WriteLine(book.Title);
Оператор new выделяет память под объект и возвращает ссылку на него. Класс – ссылочный тип, со
всеми следствиями из урока про значимые и ссылочные типы: присваивание копирует ссылку, а не
объект.
Конструктор
Создавать объект пустым, а потом заполнять поля по одному – плохо: между new и последним
присваиванием объект находится в недоделанном состоянии. Конструктор решает это:
public class Book
{
public string Title;
public string Author;
public Book(string title, string author)
{
Title = title;
Author = author;
}
}
Теперь книга не может появиться без названия и автора:
var book = new Book("Война и мир", "Толстой");
У конструктора две особенности: он называется точно так же, как класс, и не имеет типа
возврата – даже void не пишут.
Пока своего конструктора нет, компилятор подставляет пустой. Как только вы объявили конструктор с параметрами, подставленный исчезает – и new Book() перестаёт собираться. Если нужны оба варианта, объявите оба явно: это обычная перегрузка.
Несколько конструкторов
Перегрузка работает и здесь, а чтобы не дублировать код, один конструктор вызывает другой через
this:
public Book(string title, string author) : this(title, author, DateTime.Now.Year)
{
}
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
Запись : this(...) означает «сначала выполни тот конструктор, потом мой». Заполнение полей
остаётся в одном месте.
Методы
Класс хранит не только данные, но и действия над ними:
public class Book
{
public string Title;
public string Author;
public int Year;
public string GetDescription()
{
return $"{Title} – {Author}, {Year}";
}
public bool IsOlderThan(int year)
{
return Year < year;
}
}
Внутри методов класса поля доступны просто по имени – объект, к которому относится вызов, подставляется автоматически:
var book = new Book("Война и мир", "Толстой", 1869);
Console.WriteLine(book.GetDescription());
Console.WriteLine(book.IsOlderThan(1900)); // True
Разница со static-методами, которыми мы пользовались весь курс, теперь видна: GetDescription
работает с данными конкретного объекта, поэтому вызывается через него. Метод без static
вызвать без объекта нельзя – ему не над чем работать.
this
Ключевое слово this – ссылка на текущий объект. Обычно оно не нужно, но становится обязательным,
когда имя параметра совпадает с именем поля:
public class Book
{
private string title;
public Book(string title)
{
this.title = title; // слева поле, справа параметр
}
}
Именно поэтому приватные поля и принято называть с подчёркиванием: при имени _title
совпадения не возникает и this не требуется.
private: что скрыть
По умолчанию член класса виден только внутри самого класса. Слово public открывает его наружу:
public class BankAccount
{
private decimal _balance;
public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ArgumentException("Сумма должна быть положительной");
}
_balance += amount;
}
public decimal GetBalance() => _balance;
}
Поле _balance закрыто, и изменить его извне напрямую нельзя – только через Deposit, где стоит
проверка. Будь оно public, любой код мог бы записать туда отрицательное число, и никакая
проверка бы не помогла.
Это и называется инкапсуляцией: класс сам отвечает за корректность своих данных, а не надеется, что все вокруг будут аккуратны.
Соглашение об именах: приватные поля пишут с подчёркиванием и маленькой буквы (_balance), всё публичное – с большой (Deposit, Title). По одному взгляду на имя видно, откуда оно доступно.
Где объявлять класс
Класс – это отдельный тип, и его принято класть в отдельный файл с тем же именем: Book.cs для
класса Book. Технически можно объявить несколько классов в одном файле, но так не делают:
искать потом неудобно.
При инструкциях верхнего уровня классы объявляются после основного кода – по тому же правилу, что и методы.