Урок 9. Классы, объекты и свойства
До сих пор данные хранились в отдельных переменных. Но в программах часто встречаются «вещи» с несколькими свойствами сразу: у ученика есть имя, возраст и отметки, у банковского счёта — владелец и остаток. Такие вещи описывают классами.
Класс и объект
Класс — описание: какие данные есть у вещи и что она умеет. Объект (экземпляр класса) — конкретная вещь, сделанная по этому описанию. Класс «Собака» один, а объектов-собак может быть сколько угодно.
Dog rex = new Dog();
rex.Name = "Рекс";
rex.Age = 3;
rex.Bark();
Dog sharik = new Dog();
sharik.Name = "Шарик";
sharik.Bark();
Console.WriteLine($"{rex.Name}: {rex.Age} года, {sharik.Name}: {sharik.Age} лет");
class Dog
{
public string Name = "";
public int Age;
public void Bark()
{
Console.WriteLine($"{Name}: Гав!");
}
}Рекс: Гав!
Шарик: Гав!
Рекс: 3 года, Шарик: 0 летclass Dog { ... }описывает класс. В программе безMainклассы пишут в самом конце файла, после всего кода.NameиAge— поля: переменные внутри объекта. У каждого объекта они свои.Bark— метод объекта. Внутри негоName— имя именно того объекта, у которого метод вызвали.new Dog()создаёт новый объект, точка даёт доступ к его полям и методам:rex.Name,rex.Bark().- Поле, которому ничего не присвоили, получает значение по умолчанию: у
int— 0. Строке здесь сразу задано"", иначе компилятор предупредит, что в ней может оказатьсяnull.
Слово public открывает поле или метод для кода снаружи класса. Без него член класса — private: доступен только внутри класса.
Свойства
Открытые поля любой код может испортить: rex.Age = -5;. Поэтому данные прячут в private-поля, а наружу дают свойства — члены класса, которые выглядят как поля, но могут проверять значения:
class Student
{
private int age;
public int Age
{
get { return age; }
set
{
if (value < 0)
{
throw new ArgumentException("Возраст не может быть отрицательным");
}
age = value;
}
}
}get срабатывает при чтении (s.Age), set — при записи (s.Age = 15), а value — записываемое значение. throw останавливает программу с ошибкой — как такие ошибки ловить, рассказывает справка Как обработать ошибку.
Если проверять ничего не нужно, хватает короткой записи — автосвойства:
| Запись | Что значит |
|---|---|
public string Name { get; set; } = ""; | читать и менять может кто угодно |
public string Name { get; } | только чтение; значение задаётся в конструкторе |
public int Count { get; private set; } | читать — всем, менять — только внутри класса |
public int Area => Width * Height; | вычисляемое: считается при каждом чтении |
Свойства принято называть с большой буквы, private-поля — с маленькой.
Конструктор
Конструктор — особый метод, который выполняется при new. Его имя совпадает с именем класса, а типа результата у него нет. В нём объекту задают начальные значения:
BankAccount account = new BankAccount("Анна");
account.Deposit(100m);
account.Deposit(-5m);
Console.WriteLine(account.Withdraw(30m));
Console.WriteLine(account.Withdraw(500m));
Console.WriteLine($"{account.Owner}: {account.Balance}");
class BankAccount
{
private decimal balance;
public string Owner { get; }
public decimal Balance => balance;
public BankAccount(string owner)
{
Owner = owner;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
{
Console.WriteLine("Сумма должна быть больше нуля");
return;
}
balance += amount;
}
public bool Withdraw(decimal amount)
{
if (amount > balance)
{
return false;
}
balance -= amount;
return true;
}
}Сумма должна быть больше нуля
True
False
Анна: 70Остаток balance закрыт: изменить его можно только через Deposit и Withdraw, которые проверяют суммы. Это и есть инкапсуляция — объект сам следит за своими данными. Снаружи остаток можно только прочитать через свойство Balance.
Если поле и параметр называются одинаково, поле обозначают через this: this.name = name; — this значит «этот объект».
Объекты передаются по ссылке
Переменная класса хранит ссылку на объект, как переменная массива. Присваивание не копирует объект:
Point a = new Point { X = 1, Y = 2 };
Point b = a;
b.X = 10;
Console.WriteLine(a.X);
class Point
{
public int X { get; set; }
public int Y { get; set; }
}10Запись new Point { X = 1, Y = 2 } — инициализатор объекта: создать объект и сразу задать свойства. По той же причине метод, получивший объект, может изменить его, и это будет видно снаружи.
static — общее для всех объектов
Член класса со словом static принадлежит самому классу, а не отдельному объекту. Например, счётчик созданных объектов:
new Counter();
new Counter();
new Counter();
Console.WriteLine(Counter.Created);
class Counter
{
public static int Created { get; private set; }
public Counter()
{
Created++;
}
}3К static-членам обращаются через имя класса: Counter.Created, Math.Round, Console.WriteLine — всё это static-члены классов Counter, Math и Console.
Частые ошибки
- Объект не создан. В переменной или поле
null(Dog d = null;, поле-объект, которому ничего не присвоили) — объекта нет, иd.Bark()остановит программу ошибкойNullReferenceException. НужноDog d = new Dog();— подробно в справке Ошибка NullReferenceException. - Предупреждение CS8618 о свойстве-строке: оно может остаться
nullпосле конструктора. Задайте его в конструкторе или значение по умолчанию:= "";. - Обращение к
privateснаружи класса — ошибкаis inaccessible due to its protection level(CS0122). - Класс в начале файла в программе без
Main: классы должны идти после всего основного кода, иначе ошибка CS8803.
Коротко
- Класс — описание, объект — экземпляр:
Dog rex = new Dog();. - Поля хранят данные, методы — действия;
publicоткрыт снаружи,private— только внутри класса. - Свойства (
get,set) дают доступ к данным с проверкой; автосвойство —{ get; set; }. - Конструктор — метод с именем класса, выполняется при
new. - Переменная класса хранит ссылку:
b = a— один объект на две переменные.static— общее для всего класса.