O ciclo while e do-while em C# (.NET 6): um guia detalhado
ООП в C#: классы, интерфейсы и наследование с примерами
Объектно-ориентированное программирование (ООП) — это способ мышления о коде, при котором программа представляется в виде набора взаимодействующих объектов. Представь LEGO: каждый кубик — объект, у него есть форма (данные) и способность соединяться с другими кубиками (методы). Из кубиков строятся сложные конструкции.
Что такое класс
Класс — это чертёж, шаблон для создания объектов. Как чертёж дома: по одному чертежу можно построить много похожих домов, каждый со своим адресом и жителями.
// Определение класса
class Dog
{
// Поля (fields) — данные
public string Name;
public string Breed;
public int Age;
// Методы — действия
public void Bark()
{
Console.WriteLine($"{Name} говорит: Гав!");
}
public void Introduce()
{
Console.WriteLine($"Я {Name}, {Breed}, {Age} лет");
}
}
// Создание объекта (экземпляра класса)
Dog dog1 = new Dog();
dog1.Name = "Шарик";
dog1.Breed = "Дворняга";
dog1.Age = 3;
dog1.Bark(); // Шарик говорит: Гав!
dog1.Introduce(); // Я Шарик, Дворняга, 3 лет
Dog dog2 = new Dog();
dog2.Name = "Рекс";
dog2.Breed = "Немецкая овчарка";
dog2.Age = 5;
dog2.Bark(); // Рекс говорит: Гав!
Свойства: умные поля
В настоящем C#-коде редко используют публичные поля напрямую. Вместо них используют свойства — это поля с контролем доступа.
class Person
{
// Автоматическое свойство (auto-property)
public string Name { get; set; } // get — читать, set — записать
public string Email { get; set; }
// Свойство только для чтения
public string FullInfo { get; } // set отсутствует — нельзя изменить снаружи
// Свойство с валидацией
private int _age;
public int Age
{
get => _age; // Возвращаем _age
set
{
if (value < 0 || value > 150)
throw new ArgumentException("Некорректный возраст");
_age = value; // value — это то, что присваивают
}
}
// Вычисляемое свойство (только get)
public bool IsAdult => _age >= 18;
}
var person = new Person();
person.Name = "Иван";
person.Age = 25;
Console.WriteLine(person.IsAdult); // True
// person.Age = -5; // ArgumentException!
Конструкторы: инициализация объекта
class Car
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public double Price { get; private set; } // private set — снаружи не изменить
// Конструктор — метод с именем класса, без возвращаемого типа
public Car(string brand, string model, int year, double price)
{
Brand = brand;
Model = model;
Year = year;
Price = price;
}
// Перегруженный конструктор
public Car(string brand, string model) : this(brand, model, 2024, 0)
{
// : this(...) вызывает другой конструктор
}
public override string ToString()
{
return $"{Year} {Brand} {Model}";
}
}
// Создание объектов
var car1 = new Car("Toyota", "Camry", 2022, 2500000);
var car2 = new Car("Lada", "Vesta");
Console.WriteLine(car1); // 2022 Toyota Camry
Console.WriteLine(car2); // 2024 Lada Vesta
// Инициализатор объекта (object initializer) — альтернатива
var car3 = new Car("BMW", "X5", 2023, 5000000)
{
// Нельзя задать через инициализатор то, что уже задаёт конструктор,
// но можно задать дополнительные свойства с public set
};
Record: неизменяемые объекты данных
В C# 9 появился тип record — идеален для объектов-данных, которые не должны изменяться.
// Record — компактная запись данных
record Point(double X, double Y);
record Person(string Name, int Age, string City = "Москва");
var p1 = new Point(3.0, 4.0);
var person = new Person("Анна", 25);
Console.WriteLine(p1); // Point { X = 3, Y = 4 }
Console.WriteLine(person); // Person { Name = Анна, Age = 25, City = Москва }
// Records поддерживают сравнение по значению!
var p2 = new Point(3.0, 4.0);
Console.WriteLine(p1 == p2); // True! (для class было бы False)
// Создание изменённой копии с 'with'
var p3 = p1 with { X = 10.0 };
Console.WriteLine(p3); // Point { X = 10, Y = 4 }
// Class с такими же полями — сравнение по ссылке!
class PointClass { public double X; public double Y; }
var c1 = new PointClass { X = 3, Y = 4 };
var c2 = new PointClass { X = 3, Y = 4 };
Console.WriteLine(c1 == c2); // False! (разные объекты в памяти)
Struct: тип-значение
// struct — хранится в стеке (быстро, но без наследования)
struct Size
{
public int Width { get; }
public int Height { get; }
public Size(int width, int height)
{
Width = width;
Height = height;
}
public int Area => Width * Height;
}
var s1 = new Size(100, 50);
Console.WriteLine($"Размер: {s1.Width}x{s1.Height}, площадь: {s1.Area}");
// struct копируется по значению:
var s2 = s1; // Создаётся копия!
// Когда использовать struct:
// - Маленькие данные (2-4 поля)
// - Не должны быть null
// - Сравнение по значению важно
Наследование: дочерние классы
Наследование — как генеалогическое дерево. Дочерний класс наследует всё от родительского и может добавлять своё.
// Базовый (родительский) класс
class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public Animal(string name, int age)
{
Name = name;
Age = age;
}
public virtual void MakeSound() // virtual — можно переопределить
{
Console.WriteLine($"{Name} издаёт звук");
}
public void Sleep()
{
Console.WriteLine($"{Name} спит");
}
}
// Дочерний класс
class Dog : Animal // : Animal — наследуем от Animal
{
public string Breed { get; set; }
public Dog(string name, int age, string breed)
: base(name, age) // base() — вызов конструктора родителя
{
Breed = breed;
}
public override void MakeSound() // override — переопределяем
{
Console.WriteLine($"{Name} ({Breed}): Гав-гав!");
}
public void Fetch()
{
Console.WriteLine($"{Name} приносит мячик");
}
}
class Cat : Animal
{
public Cat(string name, int age) : base(name, age) {}
public override void MakeSound()
{
Console.WriteLine($"{Name}: Мяу-мяу!");
}
}
var dog = new Dog("Бобик", 3, "Хаски");
var cat = new Cat("Мурка", 2);
dog.MakeSound(); // Бобик (Хаски): Гав-гав!
dog.Sleep(); // Бобик спит (унаследовано от Animal)
dog.Fetch(); // Бобик приносит мячик (собственный метод)
cat.MakeSound(); // Мурка: Мяу-мяу!
Полиморфизм: один интерфейс — разное поведение
// Полиморфизм — разные классы, одна переменная
Animal[] animals = {
new Dog("Рекс", 4, "Овчарка"),
new Cat("Барсик", 1),
new Dog("Лайка", 2, "Хаски")
};
// Один цикл — разное поведение для каждого животного
foreach (Animal animal in animals)
{
animal.MakeSound(); // Вызывается нужная реализация!
}
// Рекс (Овчарка): Гав-гав!
// Барсик: Мяу-мяу!
// Лайка (Хаски): Гав-гав!
Абстрактные классы
// abstract — нельзя создать экземпляр, только наследовать
abstract class Shape
{
public string Color { get; set; }
// abstract метод — ОБЯЗАН быть переопределён в дочернем классе
public abstract double Area();
public abstract double Perimeter();
// Обычный метод — можно использовать как есть
public void Describe()
{
Console.WriteLine($"{GetType().Name}: площадь={Area():F2}, периметр={Perimeter():F2}");
}
}
class Circle : Shape
{
public double Radius { get; }
public Circle(double radius) { Radius = radius; }
public override double Area() => Math.PI * Radius * Radius;
public override double Perimeter() => 2 * Math.PI * Radius;
}
class Rectangle : Shape
{
public double Width { get; }
public double Height { get; }
public Rectangle(double w, double h) { Width = w; Height = h; }
public override double Area() => Width * Height;
public override double Perimeter() => 2 * (Width + Height);
}
var shapes = new Shape[] { new Circle(5), new Rectangle(4, 6) };
foreach (var shape in shapes)
{
shape.Describe();
}
// Circle: площадь=78.54, периметр=31.42
// Rectangle: площадь=24.00, периметр=20.00
Интерфейсы: контракт поведения
Интерфейс — это договор: «если ты реализуешь этот интерфейс, у тебя будут вот такие методы». Как розетка: любое устройство с вилкой подключится к розетке, неважно что это за устройство.
// Определение интерфейса (по соглашению начинается с I)
interface ISwimmable
{
void Swim(); // Метод без реализации (только сигнатура)
int SwimmingSpeed { get; } // Свойство тоже можно
}
interface IFlyable
{
void Fly();
int FlyingAltitude { get; }
}
// Класс может реализовывать НЕСКОЛЬКО интерфейсов
class Duck : Animal, ISwimmable, IFlyable
{
public Duck(string name) : base(name, 2) {}
public override void MakeSound() => Console.WriteLine($"{Name}: Кря!");
// Реализация ISwimmable
public void Swim() => Console.WriteLine($"{Name} плывёт");
public int SwimmingSpeed => 5;
// Реализация IFlyable
public void Fly() => Console.WriteLine($"{Name} летит");
public int FlyingAltitude => 100;
}
var duck = new Duck("Кряква");
duck.MakeSound(); // Кряква: Кря!
duck.Swim(); // Кряква плывёт
duck.Fly(); // Кряква летит
// Работа через интерфейс
ISwimmable swimmer = duck; // duck как пловец
swimmer.Swim();
// Проверка реализации интерфейса
if (duck is IFlyable flyer)
{
Console.WriteLine($"Летит на высоте {flyer.FlyingAltitude}м");
}
Итого
- ✅ class — ссылочный тип, передаётся по ссылке, поддерживает наследование
- ✅ struct — тип-значение, хранится в стеке, копируется при присваивании
- ✅ record — неизменяемые данные, сравнение по значению
- ✅ Свойства — умные поля с get/set и возможностью валидации
- ✅ Конструктор — метод инициализации объекта
- ✅ Наследование — дочерний класс получает всё от родительского
- ✅ virtual/override — переопределение поведения
- ✅ abstract — абстрактный класс/метод, нельзя создать экземпляр
- ✅ interface — контракт поведения, класс может реализовывать несколько
- ✅ Полиморфизм — один код работает с разными типами
Дженерики (Generics): универсальные классы
// Обобщённый (generic) класс работает с любым типом T
class Stack
{
private readonly List _items = new();
public void Push(T item) => _items.Add(item);
public T Pop()
{
if (_items.Count == 0)
throw new InvalidOperationException("Стек пуст!");
var item = _items[^1]; // Индекс с конца (C# 8+)
_items.RemoveAt(_items.Count - 1);
return item;
}
public T Peek() => _items.Count > 0 ? _items[^1]
: throw new InvalidOperationException("Стек пуст!");
public int Count => _items.Count;
public bool IsEmpty => _items.Count == 0;
public override string ToString() => $"Stack[{string.Join(", ", _items)}]";
}
// Один класс — разные типы!
var intStack = new Stack();
intStack.Push(1); intStack.Push(2); intStack.Push(3);
Console.WriteLine(intStack); // Stack[1, 2, 3]
Console.WriteLine(intStack.Pop()); // 3
Console.WriteLine(intStack.Peek()); // 2
var strStack = new Stack();
strStack.Push("Алиса"); strStack.Push("Борис");
Console.WriteLine(strStack.Pop()); // Борис
Паттерны проектирования: Singleton и Factory
// Singleton — гарантирует единственный экземпляр
class Database
{
private static Database? _instance;
private static readonly object _lock = new();
private string ConnectionString { get; }
private Database() // Приватный конструктор!
{
ConnectionString = "Server=localhost;Database=mydb";
Console.WriteLine("Подключение к БД установлено");
}
public static Database Instance
{
get
{
if (_instance is null)
{
lock (_lock)
{
_instance ??= new Database();
}
}
return _instance;
}
}
public void Query(string sql)
=> Console.WriteLine($"Выполняем: {sql}");
}
// Factory — создаёт объекты по условию
abstract class Notification
{
public abstract void Send(string message);
}
class EmailNotification : Notification
{
private string _to;
public EmailNotification(string to) => _to = to;
public override void Send(string message)
=> Console.WriteLine($"Email → {_to}: {message}");
}
class SmsNotification : Notification
{
private string _phone;
public SmsNotification(string phone) => _phone = phone;
public override void Send(string message)
=> Console.WriteLine($"SMS → {_phone}: {message}");
}
static class NotificationFactory
{
public static Notification Create(string type, string contact)
=> type.ToLower() switch
{
"email" => new EmailNotification(contact),
"sms" => new SmsNotification(contact),
_ => throw new ArgumentException($"Неизвестный тип: {type}")
};
}
// Использование
var db1 = Database.Instance;
var db2 = Database.Instance;
Console.WriteLine(ReferenceEquals(db1, db2)); // True — один объект!
db1.Query("SELECT * FROM users");
var notif = NotificationFactory.Create("email", "user@example.com");
notif.Send("Добро пожаловать!");
Event: события и подписки
// Events — сигналы между объектами
class Button
{
public string Text { get; set; }
// Событие — список подписчиков
public event EventHandler? Clicked;
public event EventHandler? ClickedWithCount;
private int _clickCount = 0;
public Button(string text) => Text = text;
public void Click()
{
_clickCount++;
Console.WriteLine($"Кнопка '{Text}' нажата (#{_clickCount})");
Clicked?.Invoke(this, EventArgs.Empty);
ClickedWithCount?.Invoke(this, _clickCount);
}
}
var btn = new Button("OK");
// Подписываемся на событие (lambda)
btn.Clicked += (sender, e) => Console.WriteLine(" → Обработчик 1: кнопка нажата!");
btn.Clicked += (sender, e) => Console.WriteLine(" → Обработчик 2: записываем в лог");
btn.ClickedWithCount += (sender, count) =>
{
if (count >= 3) Console.WriteLine($" → Внимание: нажата {count} раза подряд!");
};
btn.Click();
btn.Click();
btn.Click();