Урок 8.6 — Классы и объекты
Введение в ООП: классы, конструкторы, свойства, модификаторы доступа.
До сих пор мы писали код в одном классе Program, используя только статические методы. Но реальные программы состоят из множества взаимодействующих объектов. Объектно-ориентированное программирование (ООП) позволяет объединять данные и поведение в одной сущности — классе. Это как чертёж дома — по нему можно построить сколько угодно домов (объектов).
Введение в ООП: объект = данные + поведение
Объектно-ориентированное программирование — это подход, в котором программа представляется как набор взаимодействующих объектов. Каждый объект содержит данные (поля, свойства) и поведение (методы). Это естественный способ моделирования реального мира.
Как это выглядит в реальном мире?
Представь автомобиль. У него есть данные: цвет, марка, скорость, уровень топлива. И есть поведение: завестись, ускориться, затормозить, повернуть. Объект Car в коде будет содержать и то, и другое.
Основные принципы ООП (их четыре):
- Абстракция — выделение главных характеристик объекта, отбрасывание второстепенных.
- Инкапсуляция — скрытие внутренних данных объекта, доступ только через методы/свойства.
- Наследование — создание нового класса на основе существующего (is-a отношение).
- Полиморфизм — возможность работать с разными классами через единый интерфейс.
Синтаксис класса
Класс объявляется ключевым словом class. Внутри класса могут быть поля, свойства, методы, конструкторы и другие члены.
Базовая структура класса:
class ИмяКласса { поля; конструктор; свойства; методы; }
public class Student
{
// Поля (данные)
public string Name;
public int Age;
private double grade;
// Конструктор
public Student(string name, int age)
{
Name = name;
Age = age;
grade = 0.0;
}
// Метод (поведение)
public void SetGrade(double newGrade)
{
if (newGrade >= 0 && newGrade <= 5.0)
grade = newGrade;
}
// Свойство
public double Grade
{
get { return grade; }
set { if (value >= 0 && value <= 5.0) grade = value; }
}
// Метод, выводящий информацию
public void PrintInfo()
{
Console.WriteLine($"Студент: {Name}, возраст: {Age}, оценка: {grade:F1}");
}
}
Конструктор — инициализация объекта
Конструктор — это специальный метод, который вызывается при создании объекта (оператор new). Имя конструктора совпадает с именем класса. Конструктор нужен для начальной установки полей объекта.
Особенности конструкторов:
- Не имеют возвращаемого типа (даже
void). - Могут быть перегружены (несколько конструкторов с разными параметрами).
- Если ты не объявил ни одного конструктора, компилятор создаёт конструктор по умолчанию без параметров.
- Можно вызвать один конструктор из другого через
this().
public class Product
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
// Конструктор по умолчанию (если не объявить — создаётся автоматически)
public Product()
{
Name = "Неизвестный товар";
Price = 0.0;
Quantity = 0;
}
// Конструктор с параметрами
public Product(string name, double price)
{
Name = name;
Price = price;
Quantity = 1;
}
// Ещё один конструктор
public Product(string name, double price, int quantity)
{
Name = name;
Price = price;
Quantity = quantity;
}
// Вызов одного конструктора из другого через this
public Product(string name) : this(name, 100.0, 1)
{
// this(name, 100.0, 1) вызовет третий конструктор
}
}
// Использование:
Product p1 = new Product(); // Name="Неизвестный товар"
Product p2 = new Product("Книга", 500.0); // Name="Книга", Price=500
Product p3 = new Product("Ноутбук", 80000, 2); // полные данные
Product p4 = new Product("Часы"); // Price=100, Quantity=1
Поля и свойства (prop)
Поля — это переменные, которые хранят данные объекта. Свойства — это специальные члены класса, которые управляют доступом к полям. Они выглядят как поля извне, но внутри содержат логику (геттеры и сеттеры).
Поля (fields)
Поле — это обычная переменная, объявленная внутри класса. Может быть с модификатором доступа (public, private, protected).
public class User
{
public string Name; // поле — все могут читать и писать
private int age; // поле — доступно только внутри класса
internal string email; // поле — доступно в той же сборке
protected string phone; // поле — доступно в наследниках
}
Свойства (properties)
Свойство — это гибрид поля и метода. Оно предоставляет контролируемый доступ к данным. Внутри свойства могут быть get (чтение) и set (запись). value — это ключевое слово, обозначающее присваиваемое значение.
public class BankAccount
{
private decimal balance; // приватное поле
// Полное свойство с логикой
public decimal Balance
{
get { return balance; }
set
{
if (value >= 0)
balance = value;
else
Console.WriteLine("Баланс не может быть отрицательным!");
}
}
// Свойство только для чтения
public string AccountNumber { get; private set; }
// Авто-свойство (компилятор сам создаёт поле)
public string OwnerName { get; set; }
}
Авто-свойства (auto-implemented properties)
Если логика в get/set не нужна, можно использовать авто-свойства. Компилятор сам создаст приватное поле. Синтаксис: { get; set; }.
public class Person
{
// Авто-свойства — самый популярный способ
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
// Авто-свойство с инициализацией
public DateTime CreatedAt { get; set; } = DateTime.Now;
// Свойство только для чтения (set приватный)
public int Id { get; private set; }
// Вычисляемое свойство (только get)
public string FullName => $"{FirstName} {LastName}";
}
Методы внутри класса
Метод — это блок кода, который выполняется при вызове. Методы определяют поведение объекта. В C# методы могут быть экземплярными (вызываются на конкретном объекте) и статическими (принадлежат классу в целом).
public class Calculator
{
// Экземплярный метод — работает с данными объекта
public int Add(int a, int b)
{
return a + b;
}
// Статический метод — принадлежит классу, а не объекту
public static int Multiply(int a, int b)
{
return a * b;
}
// Метод, изменяющий состояние объекта
private int lastResult;
public void StoreResult(int result)
{
lastResult = result;
Console.WriteLine($"Сохранён результат: {lastResult}");
}
// Метод, возвращающий данные объекта
public int GetLastResult()
{
return lastResult;
}
}
// Использование:
Calculator calc = new Calculator();
int sum = calc.Add(5, 3); // экземплярный метод
int product = Calculator.Multiply(5, 3); // статический метод — через класс
calc.StoreResult(sum); // сохранение состояния
Методы могут быть перегружены (одинаковое имя, разные параметры):
public class Logger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {message}");
}
public void Log(string message, int level)
{
Console.WriteLine($"[LOG-{level}] {message}");
}
public void Log(string message, Exception ex)
{
Console.WriteLine($"[LOG-ERROR] {message}: {ex.Message}");
}
}
Ключевое слово this
this — это ссылка на текущий объект. Она используется внутри класса для обращения к его членам. Основные случаи применения:
- Разрешение конфликта имён (параметр метода совпадает с полем).
- Вызов одного конструктора из другого.
- Передача текущего объекта в качестве аргумента.
public class Employee
{
private string name;
private int age;
// Разрешение конфликта имён
public Employee(string name, int age)
{
this.name = name; // this.name — поле, name — параметр
this.age = age;
}
// Вызов конструктора через this
public Employee(string name) : this(name, 18)
{
// this(name, 18) вызывает первый конструктор
}
// Передача текущего объекта
public void Register(Company company)
{
company.AddEmployee(this); // передаём себя
}
// Явное обращение к полю (даже без конфликта)
public void PrintInfo()
{
Console.WriteLine($"Имя: {this.name}, Возраст: {this.age}");
}
}
Создание экземпляра: new ClassName()
Чтобы создать объект класса, используется оператор new. Он выделяет память и вызывает конструктор.
public class Car
{
public string Model { get; set; }
public int Year { get; set; }
public string Color { get; set; }
public Car(string model, int year)
{
Model = model;
Year = year;
Color = "Белый"; // цвет по умолчанию
}
public void Honk()
{
Console.WriteLine($"{Model}: Бип-бип!");
}
}
// Создание объектов (экземпляров класса)
Car car1 = new Car("Toyota Camry", 2022);
Car car2 = new Car("Honda Civic", 2024);
car2.Color = "Красный";
// Вызов методов на объектах
car1.Honk(); // → Toyota Camry: Бип-бип!
car2.Honk(); // → Honda Civic: Бип-бип!
// Доступ к свойствам
Console.WriteLine($"У меня {car1.Model} {car1.Year} года, цвет {car1.Color}");
// → У меня Toyota Camry 2022 года, цвет Белый
Объекты можно создавать с помощью инициализатора объектов (без явного вызова конструктора):
// Если в классе есть конструктор по умолчанию (без параметров)
// или авто-свойства, можно использовать инициализатор:
Car car3 = new Car
{
Model = "Tesla Model 3",
Year = 2023,
Color = "Синий"
};
// Или с конструктором + инициализатор:
Car car4 = new Car("BMW X5", 2025)
{
Color = "Чёрный"
};
Модификаторы доступа: public, private, protected
Модификаторы доступа определяют, какие части программы могут видеть и использовать члены класса. Это основа инкапсуляции — скрытия внутренней реализации.
Модификатор
publicprivateprotectedinternalprotected internalprivate protected
Доступен
- Везде
- Только внутри класса
- Внутри класса и в наследниках
- Внутри текущей сборки (.exe/.dll)
- Сборка + наследники
- Наследники внутри сборки
public class BankAccount
{
// public — видно всем
public string AccountNumber { get; set; }
// private — видно только внутри класса
private decimal balance;
// protected — видно в классе и наследниках
protected decimal InterestRate;
// public метод — точка доступа к приватным данным
public void Deposit(decimal amount)
{
if (amount > 0)
balance += amount;
}
public decimal GetBalance()
{
return balance; // доступ к private полю
}
}
// Наследник имеет доступ к protected, но не к private
public class SavingsAccount : BankAccount
{
public void ApplyInterest()
{
// balance += 100; // ОШИБКА! balance — private
InterestRate += 0.01m; // ОК! InterestRate — protected
}
}
private, а доступ к ним предоставляй через public свойства с логикой в get/set. Это и есть инкапсуляция.Инкапсуляция: { get; set; }
Инкапсуляция — это принцип ООП, при котором данные объекта скрыты от внешнего кода, а доступ к ним осуществляется только через публичные методы и свойства. Это защищает данные от некорректного использования.
Без инкапсуляции (плохо):
public class BadAccount
{
public decimal Balance; // публичное поле — все могут менять
// Проблема: никто не мешает установить отрицательный баланс!
}
// Использование:
BadAccount acc = new BadAccount();
acc.Balance = -1000m; // ❌ Это не имеет смысла, но C# разрешает
С инкапсуляцией (хорошо):
public class GoodAccount
{
private decimal balance; // скрытое поле
public decimal Balance // свойство с проверкой
{
get { return balance; }
set
{
if (value >= 0)
balance = value;
else
throw new ArgumentException("Баланс не может быть отрицательным");
}
}
public void Deposit(decimal amount)
{
if (amount > 0)
balance += amount;
}
public bool Withdraw(decimal amount)
{
if (amount > 0 && balance >= amount)
{
balance -= amount;
return true;
}
return false;
}
}
// Использование:
GoodAccount acc = new GoodAccount();
acc.Deposit(500);
acc.Withdraw(200);
Console.WriteLine(acc.Balance); // → 300
// acc.Balance = -100; // ❌ ArgumentException
Мини-тест
Вопрос 1: Что выведет код? var p = new Person("Анна"); Console.WriteLine(p.Name);
Правильный ответ: D (зависит от реализации конструктора)
Вопрос 2: Какой модификатор доступа делает член видимым только внутри класса?
Правильный ответ: B
Вопрос 3: Что такое свойство (property)?
Правильный ответ: B
Вопрос 4: Для чего используется ключевое слово this?
Правильный ответ: B
Вопрос 5: Что выведет код?
class Test { public int X = 5; }
var t = new Test();
t.X = 10;
Console.WriteLine(t.X);
Правильный ответ: B
Задание
Библиотека книг
Создай консольное приложение, моделирующее библиотеку:
- Создай класс
Bookс полями/свойствами:Title,Author,Year,IsAvailable(bool). - Добавь конструктор с параметрами и конструктор по умолчанию.
- Добавь метод
PrintInfo()— вывод всей информации о книге. - Создай класс
Libraryс полемList<Book> Books. - Методы Library:
AddBook(Book book),FindBook(string title),BorrowBook(string title)(меняет IsAvailable),ReturnBook(string title),PrintAllBooks(). - Используй инкапсуляцию: поле со списком книг сделай private, доступ — через методы.
- В Main() создай библиотеку, добавь несколько книг, возьми книгу, верни, выведи весь список.
Цель
Отработать создание классов, конструкторов, свойств, методов и инкапсуляцию.
Сложность
Средняя
Что важно запомнить
new, инициализирует объект. Имя совпадает с именем класса, нет возвращаемого типа.{ get; set; } — самый простой способ.public (всем), private (только классу), protected (класс + наследники).Тест: 8.6: Классы и объекты
4 вопроса