Глиф Учебники

Урок 9. Классы, объекты и свойства

Курс «C#» · урок 9 из 10 · 4 минуты чтения

До сих пор данные хранились в отдельных переменных. Но в программах часто встречаются «вещи» с несколькими свойствами сразу: у ученика есть имя, возраст и отметки, у банковского счёта — владелец и остаток. Такие вещи описывают классами.

Класс и объект

Класс — описание: какие данные есть у вещи и что она умеет. Объект (экземпляр класса) — конкретная вещь, сделанная по этому описанию. Класс «Собака» один, а объектов-собак может быть сколько угодно.

C#
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 Name string Age int Bark() метод new Dog() new Dog() rex sharik объекты — экземпляры Name "Рекс" Age 3 Name "Шарик" Age 0 по умолчанию
Класс Dog описывает поля Name, Age и метод Bark, а каждый вызов new Dog() создаёт отдельный объект со своими значениями полей.

Слово public открывает поле или метод для кода снаружи класса. Без него член класса — private: доступен только внутри класса.

Свойства

Открытые поля любой код может испортить: rex.Age = -5;. Поэтому данные прячут в private-поля, а наружу дают свойства — члены класса, которые выглядят как поля, но могут проверять значения:

C#
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. Его имя совпадает с именем класса, а типа результата у него нет. В нём объекту задают начальные значения:

C#
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.

BankAccount private balance 70 Deposit(100m) Deposit сумма больше 0? Withdraw(30m) Withdraw хватает денег? прочитать Balance только чтение напрямую
Закрытое поле balance меняется только через методы Deposit и Withdraw, которые проверяют сумму, снаружи его можно лишь прочитать через Balance, а напрямую — нельзя.

Если поле и параметр называются одинаково, поле обозначают через this: this.name = name; — this значит «этот объект».

Объекты передаются по ссылке

Переменная класса хранит ссылку на объект, как переменная массива. Присваивание не копирует объект:

C#
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 принадлежит самому классу, а не отдельному объекту. Например, счётчик созданных объектов:

C#
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.

класс Counter static Created 3 одно на всех Counter.Created по имени класса Created++ Created++ Created++ new Counter() счётчик стал 1 new Counter() счётчик стал 2 new Counter() счётчик стал 3
Свойство static Created одно на весь класс: каждый new Counter() увеличивает его на единицу, и после трёх объектов Counter.Created равно 3.

Частые ошибки

Коротко

Проверьте себя

1. Чем класс отличается от объекта?
2. Когда выполняется конструктор?
3. Что выведет программа?
C#
Box a = new Box();
a.Size = 1;
Box b = a;
b.Size = 5;
Console.WriteLine(a.Size);

class Box
{
    public int Size { get; set; }
}
4. Зачем закрывать поле словом private и давать к нему доступ через свойство?