За основу взят ролик C# 2024 С НУЛЯ ДО ПРОФИ | СЛИВ ЛУЧШЕГО КУРСА.
Некоторые главы пропущены..
Основы
Работа с консолью
Console.WriteLine(); //вывод в консоль
Console.ReadLine(); //чтение из консоль
Console.ReadKey(); //ожидания нажатия клавиши
------------
Console.InputEncoding = Encoding.Unicoide; //Если есть проблемы с вводои (на русском)
Console.OutputEncoding = Encoding.Unicode; // и выводом в консоль. (не обязательно)
string name;
Console.WriteLine("Введите имя: ");
name = Console.ReadLine();
Console.WriteLine($"Ваше имя: {name}");
Если надо, чтобы курсор был сразу после двоеточия, а не на след. строчке
Console.Write("Введите имя: "); //след.строчка, т.е. ввод в консоль будет на этой строчке.
------------
int age;
Console.Write("Ваш возраст?: ");
age = Convert.ToInt32(Console.ReadLine());
Console.WriteLine($"Вам: {age} лет!");
Инкремент и декремент
Инкремент увеличивает значение переменной на 1.
Декремент уменьшает на 1.
При подсчётах имеют высший приоритет.
int i = 0;
i++; //постфиксная форма
Console.WriteLine(i);
------------
int i = 0;
i--;
Console.WriteLine(i);
------------
int i = 0;
++i; //префиксная форма
Console.WriteLine(i);
------------
В чём разница?
Console.WriteLine(i++); //0 т.е. сначала выводит в консоль, потом меняет на 1
Console.WriteLine(++i); //1 а тут меняет на 1, потом выводит
------------
int A = 0;
Console.WriteLine(++A + 2 + 1 + A++ + "1" + ++A * 2);
т.е.
Console.WriteLine((++A) + 2 + 1 + (A++) + "1" + (++A) * 2); // 1 + 2 + 1 + 1 + "1" + 3 * 2 = 516
Практика
int money;
int food;
int foodUnitPrice = 10;
bool isAbleToPay;
Console.WriteLine($"Сегодня еда по {foodUnitPrice} монет.");
Console.Write("Сколько у вас золота? ");
money = Convert.ToInt32(Console.ReadLine());
Console.Write("Сколько еды нужно?");
food = Convert.ToInt32(Console.ReadLine());
isAbleToPay = money >= food * foodUnitPrice;
food *= Convert.ToInt32(isAbleToPay);
money -= food * foodUnitPrice;
Console.WriteLine($"У Вас {food} еды и {money} монет.");
Условные операторы
Условный оператор if
int age;
Console.Write("Введите возраст: ");
age = Convert.ToInt32(Console.ReadLine());
if (age >= 18)
{
Console.WriteLine("Добро пожаловать в бар!");
}
else
{
Console.WriteLine("Вы слишком юны.");
Console.WriteLine("Приходите к нам через " + (18 - age) + " лет.");
}
Логическое И и ИЛИ
Логическое И && (хотя бы 1 false будет давать false)
x | y | x && y (true -> 1, false -> 0) |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
Логическое ИЛИ || (хотя бы 1 true будет давать true)
x | y | x || y (true -> 1, false -> 0) |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
Условный оператор switch
switch (dayOfWeek)
{
case "понедельник":
Console.WriteLine("Идём в кино.");
break;
case "вторник":
Console.WriteLine("Проходим курсы");
break;
case "среда":
case "черверг":
Console.WriteLine("играем в игры");
break;
default:
Console.WriteLine("Нет такого дня.");
}
Random
Random random = new Random();
int number = random.Next(0, 10);
В скобках — диапазон. Первое значение включается в диапазон, а второе нет. В данном примере результат может быть от 0 до 9.
Массивы
Одномерные массивы
Набор последовательных ячеек. В каждой ячейке лежит своё значение. К каждой ячейке можно получить доступ по её номеру.
int[] cucumbers = new int[10]; //массив cucumbers из 10 пустых элементов
Console.WriteLine(cucumbers[0]); //обращение к первому элементу массива
cucumbers[7] = 13; //присвоение значения элементу
cucumbers[3] = 3;
for (int i = 0; i < cucumbers.Length; i++)
{
Console.WriteLine(cucumbers[i]);
Console.Write(cucumbers[i] + " "); //вывод в строчку
}
Random random = new Random();
for (int i = 0; i < cucumbers.Length; i++)
{
cucumbers[i] = random.Next(0, 101);
Console.Write(cucumbers[i] + " "); //вывод в строчку
}
Сокращённый инициализатор
Заполняет массив в момент инициализации теми данными, которые мы хотим.
При такой инициализации выделяется память под одномерный массив. Его размерность соответствует количеству элементов в списке инициализации.
Адрес этой области записывается в ссылочную переменную, а значения элементов массива соответствуют значениям в списке инициализатора.
Есть 2 формата записи СИ: когда мы хотим указать размер массива, и когда мы не знаем точный размер массива.
int[] cucumbers = {24, 25, 17, 38, 100, int.MaxValue, int.MinValue }; // без точного размера
int[] cucumbers = new int[4] {4, 1, 6, 7} //с точным размеров. Кол-во элементов должно соответствовать кол-ву в []
Практика с одномерными массивами
int[] array = {2, 3, 4, 7, 8};
for (int i = 0; i < arrat.Length; i++)
{
Console.WriteLine(array[i]);
}
Расчёт суммы каждого элемента
int[] array = {2, 3, 4, 7, 8};
int sum = 0;
for (int i = 0; i < arrat.Length; i++)
{
sum += array[i];
Console.WriteLine(sum);
}
Определение максимального элемента
int[] array = {1, 3, 5, 8, 12, 2, -2};
int maxElement = int.MinValue;
for (int i = 0; i < arrat.Length; i++)
{
if (maxElement < array[i])
{
maxElement = array[i];
}
}
Регистрация билетов на самолёт
int[] sectors = { 6, 28, 15, 15, 17 };
bool isOpen = true;
while (true)
{
Console.SetCursorPosition(0, 18);
for (int i = 0; i < sectors.Length; i++)
{
Console.WriteLine($"В секторе {i + 1} свободно {sectors[i]} мест.");
}
Console.SetCursorPosition(0, 0);
Console.WriteLine("Регистрация рейса");
Console.WriteLine("\n\n1 - забронировать место\n\n2 - выход\n");
Console.Write("Введите номер программы: ");
switch (Convert.ToInt32(Console.ReadLine()))
{
case 1:
int userSector, userPlaceAmount;
Console.Write("В каком секторе вы хотите лететь?: ");
userSector = Convert.ToInt32(Console.ReadLine()) - 1;
if (sectors.Length <= userSector || userSector < 0)
{
Console.WriteLine("Такого сектора нет.");
break;
}
Console.Write("Сколько мест рзабронировать?: ");
userPlaceAmount = Convert.ToInt32(Console.ReadLine());
if (userPlaceAmount < 0)
{
Console.WriteLine("Неверное кол-во мест.");
break;
}
if (sectors[userSector] < userPlaceAmount || userPlaceAmount < 0)
{
Console.WriteLine($"В секторе {userSector + 1} недостаточно мест. Остаток {sectors[userSector]}");
break;
}
sectors[userSector] -= userPlaceAmount;
Console.WriteLine("Бронирование успешно.");
break;
case 2:
isOpen = false;
break;
}
Console.ReadKey();
Console.Clear();
}
Многомерные массивы
Используют более 1го измерения.
Двумерные массивы
Их можно представить в виде таблицы или матрицы. Каждый элемент массива имеет два индекса.
- Номер строки
- Номер столбца
(нумерация с 0)
У элементов многомерного массива должен быть одинаковый тип, и они объединены под одним именем.
int[,] array = new int[3,5]; //в [] ставится , - двумерный массив
Сокращённый инициализатор
без точного размера
int[,] array = {
{1, 2, 3},
{1, 2, 3},
{7, 2, 3}
};
с точным размером
int[,] array = new int[2,3] {
{9, 8, 7},
{6, 5, 4}
};
Console.WriteLine(array[2,0]); //7 (для примера без точного размера)
Console.WriteLine(array.Length]); //9 Выдаёт кол-во элементов.
Чтобы вывести весь массив исп след.:
for (int i = 0; i < array.GetLength(0); i++) //внешний цикл для строк
{
for (int j = 0; j < array.GetLength(1); j++) //внутренний для значений строк
{
Condole.WriteLine(array[i;j]); //выдаёт в столбик
}
}
// Condole.WriteLine(array[i;j] + " "); //выдаёт в как на полочках :)
Массив — ссылочный тип
При назначении одному массиву другого, массивы начинают ссылаться на одну область памяти. Значения первого массива заменяются значениями второго.
int[] array = new int[5] {4, 5, 3, 0, 99};
int[] array = new int[3] {9, 8, 7};
array = tempArray;
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine([i] + " "); //будет 9 8 7
}
int[] petrovich = new int[3];
int[] ivanovich = new int[5];
petrovich = ivanovich; //первый земенился вторым
petrovich[0] = 3; //изменяется ivanovich
petrovich[1] = 5;
sidorovich = petrovich; //присвоился ivanovich
sidorovich[2] = 7; //изменяется ivanovich
sidorovich[0] = 10;
petrovich = new int[10]; //новый массив со старым именем
Расширение массива
Для расширения массива используется доп. массив.
Строка как массив
Тип string — массив чаров. И им можно работать как с массивом. Только есть 1 ограничение — нельзя изменять элементы.
string line = "Привет!"
Console.WriteLine(line[1]); //р
Цикл foreach
Используется для перебора массива без использования индексов элементов.
foreach (var item in collection) {};
Как читается?: для каждого var (string / int и т.д.) элемента (item) в массиве (collection) делаем какое-то действие.
Функции
Набор именованных блоков, кот. имеют список каких-то именованных параметров на вход и что-то передают на выход.
Аргумент — то, что функция получает.
Параметр — то, что функция хочет получить.
Модификаторы ref и out
Используются для передачи аргументов внутри метода или функции.
ref и out передают в функцию ссылку на область памяти и функция теребонькает уже их, а не копию.
//тут будет код..
ref требует инициализацию передаваемого элемента. out инициализирует сама и можно передавать просто int sum;
В чём отличие?
out
— это выходной параметр, а ref
— входно-выходной.
Для ref
-параметра надо передать его инициализированным, и можно пользоваться его исходным значением. А для out
-параметра не обязательно инициализировать его перед вызовом функции, нельзя использовать его значение в функции до присваивания, и обязательно нужно инициализировать его в функции.
(Таким образом, ref
-параметр немного напоминает инициализированную локальную переменную, а out
-параметр — неинициализированную.) (https://ru.stackoverflow.com/questions/480130/Чем-out-отличается-от-ref)
Перегрузки
Создание функции с одинаковыми именами, но разными сигнатурами. Если какая-то из сигнатур отличается — создаётся перегрузка.
Коллекции
Коллекции — программный объект, кот. содержит в себе набор значений одного или нескольких типов.
List
Очень похож на массив. Только обращение с листом проще.
List<int> numbers = new List<int>();
numbers.Add(12); // добавляет элемент в конец листа
numbers.Add(5);
numbers.Add(8);
numbers.Add(14);
numbers.Add(22);
numbers.AddRange(new int[] { 3, 4, 5, 6 }); // добавление целого списка
numbers.Insert(1, 123); // добавляет элемент (123) в позицию (1). Остальные элементы сдвигаются дальше на 1.
numbers.RemoveAt(3); // удаляет элемент по индексу
numbers.Remove(5); // удаляет элемент по совпадению
numbers.Clear(); // очистка листа
numbers.IndexOf(22) // выдаёт индекс(позицию) элемента
for (int = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
Queue
Очередь. Работает по алгоритму FIFO (first in forst out — первый вошёл, первый вышел)
Queue<string> patients = new Queue<string>();
patients.Enqueue("Василий"); // добавление в очередь
patients.Enqueue("Алексей");
patients.Enqueue("Роман");
patients.Enqueue("Владимир");
patients.Dequeue(); // Первый в очереди извлекается из очереди.
patients.Peek(); // Просто глянуть кто первый
Т.к. очередь неизменна, нельзя обращаться к элементам по индексу. Используется foreach.
foreach (var patient in patients)
{
Console.WriteLine(patient);
}
Stack
Похож на очередь. Только исп. алгоритм LIFO (last in first out — последний вошёл, первый вышел)
Похож на стопку книг. И нельзя вытянуть из центра книгу.
Stack<int> numbers = new Stack<int>();
numbers.Push(1); // добавление элемента
numbers.Push(2);
numbers.Push(3);
numbers.Push(4);
numbers.Push(5);
Console.WriteLine(numbers.Peek()); // показывает кто верхний в стопке
foreach (var number in numbers) // вывод без удаления
{
Console.WriteLine(number);
}
numbers.Pop(); // извлекает элемент из стека.
while (numbers.Count > 0)
{
Console.WriteLine(numbers.Pop()); // 5 4 3 2 1 - выводит в обратном порядке
}
Dictionary
Словарь.
Dictionary<string, string> countriesCapitals = new Dictionary<string, string>(); //<ключ, значение> - не обязательно одинаковые
countriesCapitals.Add("Австралия", "Канберра"); // добавление ключ-значение
countriesCapitals.Add("Турция", "Анкара");
countriesCapitals.Add("Россия", "Москва");
countriesCapitals.Remove("Турция"); // удаление (по ключу)
Console.WriteLine(countriesCapitals["Австралия"]); //не надёжно, т.к. если ошибка в ключе - будет необработанное исключение, нужна проверка:
if (countriesCapitals.ContainsKey("Австралия"))
{
Console.WriteLine(countriesCapitals("Австралия"));
}
foreach (var item in countriesCapitals) // перебор по ключ-значение
{
Console.WriteLine($"Страна - {item.Key}, столица - {item.Value}");
}
foreach (var key in countriesCapitals.Keys) // перебор по ключам
{
Console.WriteLine($"Страна - {key}"); // выводятся только ключи
}
foreach (var value in countriesCapitals.Values) // перебор по значениям
{
Console.WriteLine($"Столица - {value}"); // выводятся только значения
}
ООП
Объектно-ориентированное программирование — подход к программированию, который содержит ряд методов для оптимального решения задач разработки ПО.
Используя принципы ООП, разрабы выделяют в проектах объекты и правильно выстраивают отношения между ними.
Отношения
- Генерализация
- Реализация
- Ассоциация
- Композиция
- Агрегация
Типизация
Есть несколько техник, которые позволяют писать код с наименьшим числом ошибок:
- Система типов
- Юнит-тестирование
- Контракты
Абстракция данных
Абстрагирование — выделение существенного от несущественного. Напр.: при моделировании яблока описываются только те параметры, которые нужны программе.
Инкапсуляция
Защита внутреннего состояния объекта от внешнего неправильного использования. Соединение функции и данных, с которыми эти функции работают.
Классы и объекты
являются основным строительным материалом при построении ООПриложения на языке C#.
Класс определяет тип.
Объект определяет экземпляр этого типа.
internal class Program
{
static void Main(string[] args)
{
Car ferrari = new Car();
}
}
class Car
{
}
Поля и модификаторы доступа
Существуют следующие модификаторы доступа:
- private
- private protected
- protected
- internal
- protected internal
- public
Модификаторы | Текущий класс | Производный класс из текущей сборки | Производный класс из другой сборки | Непроизводный класс из текущей сборки | Непроизводный класс из другой сборки |
private | |||||
private protected | |||||
protected | |||||
internal | |||||
protected internal | |||||
public |
class Car
{
public string Name; //модификатор public делает доступным сам класс или член этого класса.
public int Age;
private int _horsePower; //private полная противоположность public
public float MaxSpeed;
}
Модификаторы доступа позволяют задать допустимую область видимости для членов класса. Объявление полей без модификатора = объявлению с private.
Все приватные поля начинаются с _ и с маленькой буквы. _horsePower
Публичные поля с большой буквы. Age
internal class Program
{
static void Main(string[] args)
{
Car ferrari = new Car();
fewrrari.Name = F40;
ferrari.HorsePower = 471;
ferrari.Age = 32;
ferrari.MaxSpeed = 317.0f;
}
}
class Car
{
public string Name;
public int Age;
public int HorsePower;
public float MaxSpeed;
}
Методы
Помимо данных объекты могут содержать функциональные члены, напр. методы.
Метод — функция-член, которя определена в контексте класса.
internal class Program
{
static void Main(string[] args)
{
Car ferrari = new Car();
ferrari.Name = F40;
ferrari.HorsePower = 471;
ferrari.Age = 32;
ferrari.MaxSpeed = 317.0f;
ferrari.ShowTechnicalPassport();
ferrari.BecomeOlder(5, 50);
Console.WriteLine();
ferrari.ShowTechnicalPassport();
}
}
class Car
{
public string Name;
public int Age;
public int HorsePower;
public float MaxSpeed;
public void ShowTechnicalPassport()
{
Console.WriteLine($"Название: {Name}\nЛ.С.: {HorsePower}\nВозраст: {Age}\nМакс. скор.: {MaxSpeed} км/ч");
}
public void BecomeOlder(int years, int runAwayHorses)
{
Age += years;
HorsePower -= runAwayHorses;
}
}
Через ключевое слово this можно получить ссылку на объект, кот. вызывается в методе.
class Car
{
public string Name;
public int Age;
public int HorsePower;
public float MaxSpeed;
public int Years;
public void ShowTechnicalPassport()
{
Console.WriteLine($"Название: {Name}\nЛ.С.: {HorsePower}\nВозраст: {Age}\nМакс. скор.: {MaxSpeed} км/ч");
}
public void BecomeOlder(int Years, int runAwayHorses)
{
Age += this.Years + Years; //this указывает, что используются данные которые определены в этом классе.
HorsePower -= runAwayHorses;
}
}
Конструкторы
internal class Program
{
static void Main(string[] args)
{
Car ferrari = new Car("F40", 471, 32, 317.0f);
ferrari.ShowTechnicalPassport();
}
}
class Car
{
public string Name;
public int HorsePower;
public int Age;
public float MaxSpeed;
public Car(string Name, int HorsePower, int Age, float MaxSpeed)
{
Name = name;
HorsePower = horsePower;
Age = age;
MaxSpeed = maxSpeed;
}
}
Конструктор это метод, а значит его можно перегрузить.
class Car
{
public string Name;
public int HorsePower;
public int Age;
public float MaxSpeed;
public Car(string Name, int HorsePower, int Age, float MaxSpeed)
{
if (horsePower < 0) //также защита от невалидных данных.
{
HorsePower = 1;
}
else
{
HorsePower = horsePower;
}
Name = name;
Age = age;
MaxSpeed = maxSpeed;
}
public Car() { }
}
Практика. Администрирование кафе
using System;
namespace ConsoleApp4
{
internal class Program
{
static void Main(string[] args)
{
bool isOpen = true;
Table[] tables = { new Table(1, 4), new Table(2, 8), new Table(3, 10) };
while (isOpen)
{
Console.WriteLine("Администрирование кафе.\n");
for (int i = 0; i < tables.Length; i++)
{
tables[i].ShowInfo();
}
Console.Write("\nВведите номер стола: ");
int wishTable = Convert.ToInt32(Console.ReadLine()) - 1;
Console.Write("\nВведите кол-во мест для брони: ");
int desiredPlaces = Convert.ToInt32(Console.ReadLine());
bool isReservationCompleted = tables[wishTable].Reserve(desiredPlaces);
if (isReservationCompleted)
{
Console.WriteLine("Забронировано успешно.");
}
else
{
Console.WriteLine("Бронь не прошла. Недостаточно мест.");
}
Console.ReadKey();
Console.Clear();
}
}
}
class Table
{
public int Number;
public int MaxPlaces;
public int FreePlaces;
public Table(int number, int maxPlaces)
{
Number = number;
MaxPlaces = maxPlaces;
FreePlaces = maxPlaces;
}
public void ShowInfo()
{
Console.WriteLine($"Стол: {Number}. Свободно {FreePlaces} из {MaxPlaces}.");
}
public bool Reserve(int places)
{
if (FreePlaces >= places)
{
FreePlaces -= places;
return true;
}
else
{
return false;
}
}
}
}
Связь Has-a
Когда один объект содержит в себе другой.
Есть доска, у неё есть список задач. А у задач есть список исполнителей.
При помощи связи Has-a класс Task и класс Board имеют в себе по классу:
internal class Program
{
static void Main(string[] args)
{
Performer worker1 = new Performer("Саша");
Performer worker1 = new Performer("Роман");
Task[] tasks = { new Task(worker1, "Выкопать яму."), new Task(worker2, "Вывезти грунт.") };
Board schedule = new Board(tasks);
schedule.ShowAllTasks();
}
}
class Performer
{
public string Name;
public Performer(string name)
{
Name = name;
}
}
class Board
{
public Task[] Tasks;
public Board(Task[] tasks)
{
Tasks = tasks;
}
public void ShowAllTasks()
{
for (int i = 0; i < Tasks.Lenght; i++)
{
Tasks[i].ShowInfo();
}
}
}
class Task
{
public Performer Worker;
public string Description;
public Task(Performer worker, string description)
{
Worker = worker;
Description = description;
}
public void ShowInfo()
{
Console.WriteLine($"Ответственный: {Worker.Name}\nОписание задачи: {Description}.\n")
}
}
Наследование (Is-a)
Отношение наследования. Ещё называют генерализация или обобщение.
При работе с объектами можно связывать их не только с помощью ссылок между объектами. Можно связывать объекты с помощью наследования.
Это удобно, когда есть нес-ко классов, которые имеют нечто общее, но нельзя их собрать в один.
Наследование позвоняет одному классу (наследнику) унаследовать функционал другого класса (родительского).
internal class Program
{
static void Main(string[] args)
{
}
}
class Knight //рыцарь
{
public int Health;
public int Armor;
public int Damage;
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
public void Pray()
{
Armor += 2;
}
}
class Barbarian //варвар
{
public int Health;
public int Armor;
public int Damage;
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
public void Shout()
{
Armor -= 2;
Health += 10;
}
}
У обоих классов есть много общего и у каждого есть специфичное поведение — поэтому их не выделить в отдельный класс.
Но можно создать более обобщённый класс, напр. воин.
class Warrior //воин
{
public int Health;
public int Armor;
public int Damage;
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
}
Чтобы применить наследование:
class Knight : Warrior // т.е. рыцарь наследует свойства воина, но имеет и свои плюшки.
{
public void Pray()
{
Armor += 2;
}
}
class Barbarian : Warrior
{
public void Shout()
{
Armor -= 2;
Health += 10;
}
}
Класс от которого наследуются называют базовым, а который наследует — производным.
Когда базовый класс имеет конструктор, нужно его вызвать и в производном классе. Делается это с помощью цепочки вызова конструктора.
class Warrior
{
public int Health;
public int Armor;
public int Damage;
public Warrior(int health, int armor, int damage)
{
Health = health;
Armor = armor;
Damage = damage;
}
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
}
class Knight : Warrior
{
public Knight(int health, int armor, int damage) : base(health, armor, damage) { } //вызывается конструктор базового класса
public void Pray()
{
Armor += 2;
}
}
class Barbarian : Warrior
{
int AttacSpeed;
public Barbarian(int health, int armor, int damage, int attackSpeed) : base(health, armor, damage)
{
AttacSpeed = attackSpeed;
}
public void Shout()
{
Armor -= 2;
Health += 10;
}
}
Сначала вызывается конструктор базового класса, потом добавляется специфика.
Можно применять свои значения.
public Barbarian(int health, int damage) : base(health, 5, damage)
internal class Program
{
static void Main(string[] args)
{
Knight warrior1 = new Knight(100, 10);
Barbarian warrior2 = new Barbarian(100, 1, 7, 2);
warrior1.TakeDamage(500;)
warrior2.TakeDamage(250);
Console.Write("Рыцарь: ")
warrior1.ShowInfo();
Console.Write("Варвар: ")
warrior2.ShowInfo();
}
}
class Warrior
{
protected int Health; //модификатор доступа protected, как private только с функцией наследования.
protected int Armor;
protected int Damage;
public Warrior(int health, int armor, int damage)
{
Health = health;
Armor = armor;
Damage = damage;
}
public void TakeDamage(int damage)
{
Health -= damage - Armor;
}
public void ShowInfo()
{
Console.WriteLine(Health);
}
}
class Knight : Warrior
{
public Knight(int health, int damage) : base(health, 5, damage) { }
public void Pray()
{
Armor += 2;
}
}
class Barbarian : Warrior
{
public Barbarian(int health, int armor, int damage, int attackSpeed) : base(health, armor, damage * attackSpeed) { }
public void Shout()
{
Armor -= 2;
Health += 10;
}
}
Свойства
Инкапсуляция — сокрытие данных и состояния от неправильного внешнего воздействия.
internal class Program
{
static void Main(string[] args)
{
Renderer renderer = new Renderer();
Player player = new Player(55, 10);
renderer.Draw(player.X, player.Y);
}
}
class Renderer
{
public void Draw(int x, int y, char character = '@')
{
Console.CursorVisible = false;
Console.SetCursorPosition(x, y);
Console.Write(character);
Console.ReadKey(true);
}
}
class Player
{
public int X;
public int Y;
public Player( int x, int y)
{
X = x;
Y = y;
}
}
Всё работает, @ рисуется по координатам. Но, представим ситуацию, что кто-то поменял координаты:
static void Main(string[] args)
{
Renderer renderer = new Renderer();
Player player = new Player(55, 10);
player.X = -1000;
player.Y = -500;
renderer.Draw(player.X, player.Y);
}
теперь прога выдаёт кучу ошибок. Как защитить прогу?
Можно координаты инкапсулировать. (сделать приватными)
class Player
{
private int _x;
private int _y;
public Player( int x, int y)
{
_x = x;
_y = y;
}
}
Но теперь не можем обращаться к этим полям и передать их отрисовщику.
Чтобы решить проблему можно:
1. Сделать методы для получения координат.
class Player
{
private int _x;
private int _y;
public Player( int x, int y)
{
_x = x;
_y = y;
}
public int GetPositionX()
{
return _x;
}
public int GetPositionY()
{
return _y;
}
}
и в проге можно вызывать эти методы.
static void Main(string[] args)
{
Renderer renderer = new Renderer();
Player player = new Player(55, 10);
renderer.Draw(player.GetPositionX(), player.GetPositionY());
}
это неудобно, поэтому есть свойства.
2. Сделать обслуживаемые или автореализуемые свойства.
class Player
{
private int _x;
private int _y;
public int X
{
get //получать. Выполняется когда кто-то обращается к этому X.
{
return _x;
}
set //устанавливать
{
_x = value; // value - это то, что поставится после равно, когда обратятся к X и захотят поставить значение.
}
}
public Player( int x, int y)
{
_x = x;
_y = y;
}
}
т.е. теперь можно получить доступ к приватным полям.
renderer.Draw(player.X, player.Y);
а если сделать private set — доступа на запись не будет.
private set
{
_x = value;
}
внутри этих блоков можно делать свои условия:
if(X >0 && X < 100)
_x = value;
лучше не нагружать эти блоки лишним кодом, а вообще сократить их до 1 строчки. Это называется автореализуемым свойством.
public int Y { get; private set; }
такая запись равносильна этой:
public int X
{
get
{
return _x;
}
set
{
_x = value;
}
}
В итоге можно сделать set внутри класса Player:
class Player
{
public int X { get; private set; }
public int Y { get; private set; }
public Player( int x, int y)
{
X = x;
Y = y;
}
}
Проблема возвращения ссылки на массивы
internal class Program
{
static void Main(string[] args)
{
Cart cart = new Cart();
cart.ShowProducts();
List<Product> product = new List<Product>();
for (int i = 0; i < cart.GetProductsCount(); i++)
{
products.Add(cart.GetProductByIndex(i));
}
products.RemovaAt(0);
Console.WriteLine();
cart.ShowProducts();
}
}
class Cart
{
private List<Product> _products = new List<Product>();
public Cart()
{
_products.Add(new Product("Яблоко"));
_products.Add(new Product("Банан"));
_products.Add(new Product("Апельсин"));
_products.Add(new Product("Груша"));
}
public void ShowProducts()
{
foreach (var product in _products)
{
Console.WriteLine(product.Name);
}
}
public int GetProductsCount()
{
return _products.Count
}
public Product GetProductByIndex(int index)
{
return _products.ElementAt(index);
}
class Product
{
public string Name { get; private set; }
public Product(string name)
{
Name = name;
}
}
Практика. Бой героев при помощи классов
internal class Program
{
static void Main(string[] args)
{
Fighter[] fighters =
{
new Fighter("Джон", 500, 50, 0),
new Fighter("Марк", 50, 25, 20),
new Fighter("Алекс", 150, 100, 10),
new Fighter("Джек", 300, 75, 5),
};
int fighterNumber;
for (int i=0; i < fighters.Length; i++)
{
Console.Write(i + 1 + " ");
fighters[i].ShowStats();
}
Console.WriteLine("\n** " + new string('-', 25) + " **");
Console.Write("\nВыберите первого бойца: ");
fighterNumber = Convert.ToInt32(Console.ReadLine()) - 1;
Fighter firstFighter = fighters[fighterNumber];
Console.Write("Выберите второго бойца: ");
fighterNumber = Convert.ToInt32(Console.ReadLine()) - 1;
Fighter secondFighter = fighters[fighterNumber];
Console.WriteLine("\n** " + new string('-', 25) + " **");
while(firstFighter.Health > 0 && secondFighter.Health > 0)
{
firstFighter.TakeDamage(secondFighter.Damage);
secondFighter.TakeDamage(firstFighter.Damage);
firstFighter.ShowCurrentHealth();
secondFighter.ShowCurrentHealth();
}
}
}
class Fighter
{
private string _name;
private int _health;
private int _damage;
private int _armor;
public int Health
{
get
{
return _health;
}
}
public int Damage
{
get { return _damage; }
}
public Fighter(string name, int health, int damage, int armor)
{
_name = name;
_health = health;
_damage = damage;
_armor = armor;
}
public void ShowStats()
{
Console.WriteLine($"Боец: {_name}, здоровье: {_health}, урон: {_damage}, броня: {_armor}");
}
public void ShowCurrentHealth()
{
Console.WriteLine($"{_name} здоровье: {_health}");
}
public void TakeDamage(int damage)
{
_health -= damage - _armor;
}
}
Это похоже на тот бой, который делали до раздела ООП. И теперь будем знать как этот пример реализовать при помощи классов.
На этом примере посмотрели, что можем присвоить одного бойца другому, сделали новую переменную для бойца, а потом в эту переменную поместили нужного бойца из массива (выдернули из списка) и поместили на арену.
Раньше не могли сделать выбор бойца, а теперь код позволяет это сделать.
Виртуальные методы
методы базового класса, реализация которых может изменяться в производных классах.
internal class Program
{
static void Main(string[] args)
{
}
}
class NonPlayerCharacter
{
public void ShowDescription()
{
Console.WriteLine("Я простой NPC");
}
}
class Farmer
{
}
class Knight
{
}
class Child
{
}
эти персонажи имеют разные ответственности. И поведение у них будет разное. И поэтому мы бы хотели сделать разные описания и вызывать описания по одному методу ShowDescription().
В этом случае метод ShowDescription можно пометить как виртуальный.
public virtual void ShowDescription()
{
Console.WriteLine("Я простой NPC.");
}
после этого надо пометить, что все персонажи — это NPC
class Farmer : NonPlayerCharacter
{
}
class Knight : NonPlayerCharacter
{
}
class Child : NonPlayerCharacter
{
}
теперь нужно для каждого переопределить (если надо) виртуальный метод.
class Farmer : NonPlayerCharacter
{
public override void ShowDescription()
{
base.ShowDescription();
Console.WriteLine("Я фермер, работаю на поле.");
}
}
class Knight : NonPlayerCharacter
{
public override void ShowDescription()
{
Console.WriteLine("Я рыцарь.");
}
}
class Child : NonPlayerCharacter
{
}
internal class Program
{
static void Main(string[] args)
{
NonPlayerCharacter[] characters =
{
new NonPlayerCharacter(),
new Farmer(),
new Knight(),
new Child()
};
foreach (var character in characters)
{
character.ShowDescription();
Console.WriteLine(new String('-', 40));
}
}
}
class NonPlayerCharacter
{
public virtual void ShowDescription()
{
Console.WriteLine("Я простой NPC.");
}
}
class Farmer : NonPlayerCharacter
{
public override void ShowDescription()
{
base.ShowDescription();
Console.WriteLine("Я фермер, работаю на поле.");
}
}
class Knight : NonPlayerCharacter
{
public override void ShowDescription()
{
Console.WriteLine("Я рыцарь.");
}
}
class Child : NonPlayerCharacter
{
}
Цикл обновления
Полиморфизм подтипов относится к Ad hoc (лат. «специально для этого») полиморфизму. Когда мы исполняем разный код для примерно одинаковыцх типов.
class Behavior
{
public virtual void Update()
{
}
}
class Walker : Behavior
{
public override void Update()
{
Console.WriteLine("Иду");
}
}
class Jumper : Behavior
{
public override void Update()
{
Console.WriteLine("Прыгаю");
}
}
таким образом есть цикл выполнения, и мы можем массивом определить ряд поведений, в котором будут храниться сущности, которые обладают своим поведением из ряда.
И для этого в блоке Main:
internal class Program
{
static void Main(string[] args)
{
Behavior[] behaviours =
{
new Walker(),
new Jumper()
}
while (true)
{
foreach(var behaviour in behaviours)
{
behaviour Update();
System.Threading.Thread.Sleed(1000); //1 секунда = 1000 миллисекунд
}
}
}
}
Таким оборазом, мы целиком пробегаемся по массиву и у каждой из сущностей вызываем метод Update.
Интерфейсы
Интерфейс представляет некий набор методов, которые не имеют реализаций.
Как интерфейсы описываются:
interface IMovable
{
}
Интерфейс IMovable отвечает за некоторое действие, которое относится к движению. У него будет метод, который ничего не возвращает и отвечать за то, чтобы что-то двигать:
interface IMovable
{
void Move();
}
Как это применяется:
class Car : IMovable
{
public void Move() //если не реализовать все методы определённые внутри интерфейса будет ошибка.
{
}
}
Если от классов можно наследовать только один раз, то от интерфейса можно много (через запятую):
class Car : Vehicle, IMovable
internal class Program
{
static void Main(string[] args)
{
IMovable car = new Car();
IMovable man = new Human();
}
}
interface IMovable
{
void Move();
void ShowMoveSpeed();
}
interface IBurnamle
{
void Burn();
}
class Car : Vehicle, IMovable, IBurnamle
{
public void Move() { }
public void ShowMoveSpeed() { }
public void Burn() { }
}
class Human : IMovable
{
public void Move() { }
public void ShowMoveSpeed() { }
}
Абстрактные классы
некоторые чертежи для будущих классов.
От абстрактных классов только наследуются, т.к. экземпляр создать нельзя.
В АК толже может быть один или несколько нереализованных методов.
abstract class Vehicle
{
protected float Speed;
public abstract void Move(); //некоторый метод у кот. нет реализыции и в то же время он абстрактный.
}
Также в контексте абстрактного класса помимо абстр методов и полей можно делать какие-то свои методы (кот имеют реализацию).
abstract class Vehicle
{
protected float Speed;
public abstract void Move();
public float GetCurrentSpeed()
{
return Speed;
}
}
class Car : Vehicle
{
public override void Move()
{
Console.WriteLine("Машина едет по асфальту")
}
}
В чём отличие от интерфейса?:
В интерфейсе нельзы сделать поле, нельзя создать уже реализованные методы. …
Статические члены
В C# все члены отличаются по отношению к классу и к объекту.
class User
{
public void ShowInfo() //при таком определении мы будем говорить о методе, кот принадлежит объекту.
{
}
}
Это означает, что мы можем использовать этот член в контексте объекта при обращении через сам объект с помощью оператора точка.
static void Main(string[] args)
{
User user = new User();
user.ShowInfo();
}
Член класса явл членом доступным в некотором глобальном контексте. Мы можем его исп без создания объекта, обращаясь через точку.
Если в контексте класса определим член с ключевым словом static, то он становится членом класса.
class User
{
public static int identifications;
public int Identification;
public User()
{
Identification = ++Identifications;
}
public void ShowInfo()
{
Console.WriteLine(Identification);
}
}
Это позволит при создании объектов создавать новые уникальные идентификаторы.
static void Main(string[] args)
{
User user1 = new User();
User user2 = new User();
user1.ShowInfo(); //1
user1.ShowInfo(); //2
User.Identifications = 10;
}
static void Main(string[] args)
{
User.Identifications = 10;
User user1 = new User();
User user2 = new User();
user1.ShowInfo(); //11
user1.ShowInfo(); //12
}
Распространённая ошибка при работе со статическими методами
Попытка работы с нестатическим методом из статического метода
Напр. у того же пользователя есть поле, кот определяет его зарплату за час.
class User
{
public static int identifications;
public int Identification;
public int MenHourPrice;
public User()
{
Identification = ++Identifications;
}
public void ShowInfo()
{
Console.WriteLine(Identification);
}
public int GetSalaryPerDay(int workedHours) //получить зп за день
{
return MednHourPrice * workedHours;
}
public int GetSalaryPerMonth(int workedDays) //получить зп за месяц
{
return GetSalaryPerDay(8) * workedDays;
}
}
как только GetSalaryPerMonth станет статическим (public static int GetSalaryPerMonth(int workedDays)) — получим ошибку.
тогда нужно изменить GetSalaryPerDay на ститический (public static int GetSalaryPerDay(int workedHours))
и public int MenHourPrice; сделать статическим (public static int MenHourPrice;)
Но таким образом нельзя будет создать несколько объектов, каждый из кот имеет свою зп в час.
При этом вызвать статический метод из динамического можно. Потому, что работаем в контексте объекта, но не будем исп глобальный метод.
Статический конструктор
Если есть какие-то статические состояния и методы — надо эти состояния проинициализировать. За инициализацию отвечает статический конструктор. Он является контрактом конструирования. Это работает и со статическими членами.
СК можно размещать в рамках класса.
class MyClass
{
static MyClass() // не имеет модификаторов доступа, т.к. явно его вызвать нельзя.
{
}
}
СК не может содержать параметров, т.к. явно его вызвать нельзя. Он может не содержаться вообще, или можно его разместить в одном экземпляре. СК вызывается в тот момент, когда мы используем наш тип.
Интересное из собеседований:
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Привет, мир!")
MyClass instance = new MyClass();
Console.WriteLine(MyClass.StaticField);
}
}
class MyClass
{
public static float StaticField;
static MyClass()
{
StaticField = 10;
Console.WriteLine("Статический конструктор.")
}
public MyClass()
{
Console.WriteLine("Обычный конструктор.")
}
}
Вопрос такой: в какой последовательности будут выводиться сообщения?
Ответ:
Привет, мир!
Статический конструктор.
Обычный конструктор.
10
Здесь используется техника «ленивая инициализация». Когда вызываем СК только в момент первого взаимодействия с типом. Если обычный конструктор будет исп статическое состояние, то нужно до этого проинициализировать статическое состояние.
Также можно подумать, что СК вызывается в момент обращения к статическому полю — это тоже не так, до этого он тоже вызовется.
Статика — это плохо
Инфа из раздела Статические члены открывает путь к невероятно простому способу написания программы. А именно использования глобального состояния и размещения функции где попало. Но это ведёт к багам и монолитной, нерасширяемой системе.
Всё глобальное можно разбить на композицию объектов.
Структура vs класс
При описании типов можно использовать структуру. Структура — сходное с классами средство, которое отличается семантикой.
Объект класса — сущность. И он не равен другому объекту, даже если все поля этих объектов имеют одинаковые значения.
Объект структуры — это объект значения. И он равен другому объекту, если значения полей совпадают.
Пример: класс — два пользователя с одинаковыми фио, и структура — две точки в пространстве с одинаковыми координатами.
Определение структур
struct Vector2
{
int x, y; //у структуры все члены по умолчанию публичны. Но указывать их нужно.
}
struct Vector2
{
public int x, y;
}
У структур по умолчанию есть конструкторы. Т.е. мы имеем объект структуры без явного вызова конструктора.
Можно заполнять Vector2 как угодно:
Vector2 position;
Vector2 position = new Vector2();
Vector2 position = default(Vector2);
position.X = 10;
Структура — тип значения. Он не может иметь значение null.
Можно задать значение по умолчанию через конструктор:
struct Vector2
{
public int x, y;
public Vector2(int x, int y)
{
X = x;
Y = y;
}
}
но нельзя делать пустой конструктор — будет ошибка. Если надо сделать конструктор только для одного объекта:
public Vector2(int x) : this() //для Y берётся конструктор по умолчанию.
{
X = x;
}
Распространённые ошибки при работе со структурами
Основная ошибка — забыть, что структуры это тип значения.
internal class Program
{
static void Main(string[] args)
{
Vector2 targetPosition = new Vector2(10, 10);
Vector2 playerPosition = targetPosition;
playerPosition.X += 15;
Console.WriteLine(targetPosition.X) //10
}
}
struct Vector2
{
public int x, y;
public Vector2(int x, int y)
{
X = x;
Y = y;
}
}
internal class Program
{
static void Main(string[] args)
{
Vector2 targetPosition = new Vector2(10, 10);
Vector2 playerPosition = targetPosition;
playerPosition.X += 15;
Console.WriteLine(targetPosition.X) //25
}
}
class Vector2
{
public int x, y;
public Vector2(int x, int y)
{
X = x;
Y = y;
}
}
В случае с класссами мы имеем один объект на 2 переменные.
А в случае со структурой мы имеем копирование значения. Будет 2 объекта и каждая переменная работает со своим объектом.
Аналогичная ошибка, не такая явная:
internal class Program
{
static void Main(string[] args)
{
GameObject bullet = new GameObject();
bullet.position.X = 50; //кто-то выстрелил и пуля сместилась будет ошибка
}
}
class GameObject //класс, кот описывает игровой объект
{
public Vector2 position { get; set; } //автореализуемое свойство
public Vector2 GetPosition()
{
return new Vector2(10, 10); //возвращает некий объект структуры
}
}
struct Vector2
{
public int x, y;
public Vector2(int x, int y)
{
X = x;
Y = y;
}
}
будет ошибка, потому что public Vector2 GetPosition() возвращает некий объект структуры и мы не можем его изменить. Потому что возвращается копия структуры.
Надо вот так:
internal class Program
{
static void Main(string[] args)
{
GameObject bullet = new GameObject();
Console.WriteLine(bullet.Position.X); //0
Vector2 newPosition = bullet.Position;
newPosiiton.X = 50;
bullet.Position = newPosiiton;
Console.WriteLine(bullet.Position.X); //50
}
}
class GameObject //класс, кот описывает игровой объект
{
public Vector2 position { get; set; }
}
struct Vector2
{
public int x, y;
public Vector2(int x, int y)
{
X = x;
Y = y;
}
}
Практика. Создание компьютерного клуба
using System;
using System.Collections.Generic;
namespace ConsoleApp4
{
internal class Program
{
static void Main(string[] args)
{
ComputerClub computerClub = new ComputerClub(8);
computerClub.Work();
}
}
class ComputerClub
{
private int _money = 0;
private List<Computer> _computers = new List<Computer>();
private Queue<Client> _clients = new Queue<Client>();
public ComputerClub(int computersCount)
{
Random random = new Random();
for (int i = 0; i < computersCount; i++)
{
_computers.Add(new Computer(random.Next(5, 15)));
}
CreateNewClients(25, random);
}
public void CreateNewClients(int count, Random random)
{
for (int i = 0; i < count; i++)
{
_clients.Enqueue(new Client(random.Next(100, 250), random));
}
}
public void Work()
{
while (_clients.Count > 0)
{
Client newClient = _clients.Dequeue();
Console.WriteLine($"Баланс клуба: {_money} руб. Ждём клиента.");
Console.WriteLine($"Новый клиент. Хочет {newClient.DesiredMinutes} минут.");
ShowAllComputersState();
Console.Write("\nВы предлагаете ему комп под номером: ");
string userInput = Console.ReadLine();
if (int.TryParse(userInput, out int computerNumber))
{
computerNumber -= 1;
if(computerNumber >= 0 && computerNumber < _computers.Count)
{
if (_computers[computerNumber].IsTaken)
{
Console.WriteLine("Уже занято. Клиент ушёл.");
}
else
{
if (newClient.CheckSolvency(_computers[computerNumber]))
{
Console.WriteLine("Уплочено. Занял " + computerNumber + 1);
_money += newClient.Pay();
_computers[computerNumber].BecomeTaken(newClient);
}
else
{
Console.WriteLine("Не хватило денег. Ушёл.");
}
}
}
else
{
Console.WriteLine("Вы сами не знаете за какой комп посадить клиента. Он ушёл. :(");
}
}
else
{
CreateNewClients(1, new Random());
Console.WriteLine("Неверный ввод.");
}
Console.WriteLine("Для след клиента тыкай кнопку.");
Console.ReadKey();
Console.Clear();
SpendOneMinute();
}
}
private void ShowAllComputersState()
{
Console.WriteLine("\nСписок компов:");
for (int i = 0; i < _computers.Count; i++)
{
Console.Write(i + 1 + " - ");
_computers[i].ShowState();
}
}
private void SpendOneMinute()
{
foreach (var computer in _computers)
{
computer.SpendOneMinute();
}
}
}
class Computer
{
private Client _client;
private int _minutesRemaining;
public bool IsTaken
{
get
{
return _minutesRemaining > 0;
}
}
public int PricePerMinute { get; private set; }
public Computer(int pricePerMinute)
{
PricePerMinute = pricePerMinute;
}
public void BecomeTaken(Client client)
{
_client = client;
_minutesRemaining = _client.DesiredMinutes;
}
public void BecomeEmpty()
{
_client = null;
}
public void SpendOneMinute()
{
_minutesRemaining--;
}
public void ShowState()
{
if (IsTaken)
Console.WriteLine($"Комп занят. Осталось {_minutesRemaining} минут.");
else
Console.WriteLine($"Комп свободен. Цена за минуту: {PricePerMinute}");
}
}
class Client
{
private int _money;
private int _moneyToPay;
public int DesiredMinutes { get; private set; }
public Client(int money, Random random)
{
_money = money;
DesiredMinutes = random.Next(10, 30);
}
public bool CheckSolvency(Computer computer)
{
_moneyToPay = DesiredMinutes * computer.PricePerMinute;
if (_money >= _moneyToPay)
{
return true;
}
else
{
_moneyToPay = 0;
return false;
}
}
public int Pay()
{
_money -= _moneyToPay;
return _moneyToPay;
}
}
}
Явное и неявное преобразование
Есть 2 термина: Слабая / сильная типизация и статическая / динамическая.
Статически типизированный язык означает, что тип переменной должен быть известен на этапе компиляции. С# — статически типизированный язык.
А термин слабой и сильной типизации означает может ли значение менять свой тип в процессе программы. C# — сильно типизированный язык. Если значение приобрело какой-то тип, в дальнейшем поменять его нельзя. Но это постоянно происходит. Для этого есть 2 инструмента: явная и неявная типизация.
string message = "Hello world";
int age = 18;
age = message; //ошибка
long age2 = age; //неявное преобразование (любой int меньше long и вписывается в границы)
Неявное преобразование бывает когда можно без последствий соотнести один тип к другому (конвертация без потери).
int a = -15;
long b = long.MaxValue;
a = (int)b; //явное преобразование. Long будет сокращён(обрезан) до int.
Upcasting и Downcasting
class Person
{
public string Name { get; private set; }
public Person(string name)
{
Name = name;
}
public void ShowName()
{
Console.WriteLine("Я " + Name);
}
}
class Mentor : Person
{
public int NumberOfStudents {get; private set;}
public Mentor(string name, int numberOfStudents) : base(name)
{
NumberOfStudents = numberOfStudents;
}
}
class Student : Person
{
public int AverageScore {get; private set; }
public Student(string name, int averageScore) : base(name)
{
AverageScore = averageScore;
}
}
Для преобразования объектов классов есть восходящее (Upcasting) и нисходящее (Downcasting) преобразование.
Upcasting
Надо всегда помнить, что объекты производного типа (в этом примере Mentor и Student) всегда являются объектами базового типа (Person).
Если Mentor / Student являются Person, то можно сказать, что переменная Person может хранить в себе ссылку на объект типа Mentor.
internal class Program
{
static void Main(string[] args)
{
Person person;
Mentor mentor = new Mentor("Tom", 8);
person = mentor; //теперь человек явл ментором, но вбирает в себя только ту часть которая есть в менторе и относится к человеку.
}
}
если сделать mentor = person будет ошибка, чтобы сделать так надо
Downcasting
Нисходящее преобразование работает наоборот. От базового класса двигаемся к производному.
internal class Program
{
static void Main(string[] args)
{
Person person = new Student("Jim", 5); //надо присвоить человеку значение студента. (в переменную человек присвоить значение, кот = объекту студента.)
Student student;
student = (Student)person; //Явное преобразование
}
}
Без upcasting невозможен downcasting.
internal class Program
{
static void Main(string[] args)
{
Person person;
Mentor mentor = new Mentor("Tom", 8);
person = mentor;
mentor = (Mentor)person;
Console.WriteLine(mentor.NumberOfStudents);
}
}
Восходящее преобразование проходит неявно. Нисходящее — надо идти от базового к производному, явно указывать тип к которому преобразуется и до этого сделать upcasting.
as и is
Как преобразовывать типы без ошибок, которые не видно в редакторе, но при этом всплывают при работе проги?
- Использование as
internal class Program
{
static void Main(string[] args)
{
Person person = new Person("Jim");
Mentor mentor = person as Mentor;
Student student = person as Student;
}
}
as пытается преобразовать из одного типа в другой без выброса ошибки.
Если преобразование не удалось — в переменную запишется null.
Если удалось — запишется то, что преобразовалось.
Для эксперимента можно:
if (mentor != null)
{
mentor.ShowName();
Console.WriteLine(mentor.NumberOfStudents);
}
if (student != null)
{
student.ShowName();
Console.WriteLine(student.AverageScore);
}
Ничего не выведется, т.к. изначально создавался Person. И as записал в переменную null.
- Использование is
internal class Program
{
static void Main(string[] args)
{
Person person = new Person("Jim");
Mentor mentor;
Student student;
if (person is Mentor)
{
mentor = (Mentor)person;
mentor.ShowName();
Console.WriteLine(mentor.NumberOfStudents);
}
}
}
is проверяет можно ли вообще преобразовывать.
Pattern matching (Сопоставление шаблонов)
internal class Program
{
static void Main(string[] args)
{
Person person = new Student("Jim", 5);
if (person is Student student) //равносильно предыдущему примеру.
{
student.ShowName();
Console.WriteLine(student.AverageScore);
}
}
}
if (person is Student student) — после проверки сразу создаётся переменная и записывается результат того к чему нужно преобразоваться.
Можно дальше продолжать if else и проверять на соответствие типов, но есть удобнее вариант:
Person person = new Student("Jim", 5);
switch (person)
{
case Mentor mentor:
mentor.ShowName();
Console.WriteLine(mentor.NumberOfStudents);
break;
case Student student:
student.ShowName();
Console.WriteLine(student.AverageScore);
break;
}
Enum
Часто встречается ситуация, когда есть некоторые повторяемые значения..
internal class Program
{
static void Main(string[] args)
{
List<Game> games = new List<Game>();
games.Add(new Game("Black & White", "Strategy"));
games.Add(new Game("Witcher 3: Wild Hunt", "RPG"));
games.Add(new Game("Sid Meier's Civilization VI", "Strategy"));
games.Add(new Game("Ori and the Will of Wisps", "Action"));
}
}
class Game
{
private string _title;
private string _genre;
public Game(string title, string genre)
{
_title = title;
_genre = genre;
}
public void ShowInfo()
{
Console.WriteLine($"Это игра: (_title), её жанр: {_genre}");
}
}
Есть поля имя и жанр. При создании списка нужно вручную вводить все поля. И одинаковые жанры надо вводить каждый раз..
Enum — тип. Тип перечисления. Тип-значение, определяется набором именованных констант.
internal class Program
{
static void Main(string[] args)
{
List<Game> games = new List<Game>();
games.Add(new Game("Black & White", Genre.Strategy));
games.Add(new Game("Witcher 3: Wild Hunt", Genre.RPG));
games.Add(new Game("Sid Meier's Civilization VI", Genre.Strategy));
games.Add(new Game("Ori and the Will of Wisps", Genre.Action));
}
}
Enum Genre
{
Strategy, //за значениями скрываются числа. Начинаются с 0. (Мосжно ставит свои (Strategy = 1,))
RPG,
Action
}
class Game
{
private string _title;
private Genre _genre;
public Game(string title, Genre genre)
{
_title = title;
_genre = genre;
}
public void ShowInfo()
{
Console.WriteLine($"Это игра: (_title), её жанр: {_genre}");
}
}
LINQ
Простой и удобный язык запросов к любым источникам данных.
Цель языка LINQ — обеспечить простоту написания запросов ко многим источникам данных.
Пример фильтрации игроков у кот. уровень больше 200:
using ConsoleApp4;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp4
{
internal class Program
{
static void Main(string[] args)
{
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Билл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
//List<Player> filteredPlayers = new List<Player>();
//foreach (var player in players)
//{
// if (player.Level > 200)
// filteredPlayers.Add(player);
//}
var filteredPlayers = from Player player in players where player.Level > 200 select player;
foreach (var player in filteredPlayers)
{
Console.WriteLine(player.Name);
}
}
}
class Player
{
public string Name { get; private set; }
public int Level { get; private set; }
public Player(string login, int level)
{
Name = login;
Level = level;
}
}
}
Методы расширения LINQ
Select: определяет что добавить в выборку
Where: определяет фильтр выборки
OrderBy: упорядочивает элементы по возрастанию
OrderByDescending: упорядочивает элементы по убыванию
ThenBy: задает дополнительные критерии для упорядочивания элементов возрастанию
ThenByDescending: задает дополнительные критерии для упорядочивания элементов по убыванию
Join: соединяет две коллекции по определенному признаку
GroupBy: группирует элементы по ключу
ToLookup: группирует элементы по ключу, при этом все элементы добавляются в словарь
GroupJoin: выполняет одновременно соединение коллекций и группировку элементов по ключу
Reverse: располагает элементы в обратном порядке
All: определяет, все ли элементы коллекции удовлетворяют определенному условию
Any: определяет, удовлетворяет хотя бы один элемент коллекции определенному условию
Contains: определяет, содержит ли коллекция определенный элемент
Distinct: удаляет дублирующиеся элементы из коллекции
Except: возвращает разность двух коллекций, т.е. из коллекции 1 исключает элементы коллекции 2
Union: объединяет две однородные коллекции
Intersect: возвращает пересечение двух коллекций, то есть те элементы, которые встречаются в обоих коллекциях
Count: подсчитывает количество элементов коллекции, которые удовлетворяют определенному условию
Sum: подсчитывает сумму числовых значений в коллекции
Average: подсчитывает cреднее значение числовых значений в коллекции
Min: находит минимальное значение
Max: находит максимальное значение
Take: выбирает определенное количество элементов
Skip: пропускает определенное количество элементов
TakeWhile: возвращает цепочку элементов последовательности, до тех пор, пока условие истинно
SkipWhile: пропускает элементы в последовательности, пока они удовлетворяют заданному условию, и затем возвращает оставшиеся элементы
Concat: объединяет две коллекции
Zip: объединяет две коллекции в соответствии с определенным условием
First: выбирает первый элемент коллекции
FirstOrDefault: выбирает первый элемент коллекции или возвращает значение по умолчанию
Single: выбирает единственный элемент коллекции, если коллекция содердит больше или меньше одного элемента, то генерируется исключение
SingleOrDefault: выбирает первый элемент коллекции или возвращает значение по умолчанию
ElementAt: выбирает элемент последовательности по определенному индексу
ElementAtOrDefault: выбирает элемент коллекции по определенному индексу или возвращает значение по умолчанию, если индекс вне допустимого диапазона
Last: выбирает последний элемент коллекции
LastOrDefault: выбирает последний элемент коллекции или возвращает значение по умолчанию
Пример:
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Билл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
//List<Player> filteredPlayers = new List<Player>();
//foreach (var player in players)
//{
// if (player.Level > 200)
// filteredPlayers.Add(player);
//}
var filteredPlayers = from Player player in players where player.Level > 200 select player;
var filteredPlayers2 = players.Where(player => player.Level > 200).Select(player => player);
var filteredPlayers3 = players.Where(player => player.Name.ToUpper().StartsWith("K"));
foreach (var player in filteredPlayers3)
{
Console.WriteLine(player.Name);
}
Сортировка
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Билл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
var orderedPlayersByLevel = players.Where(player => player.Level > 100).OrderBy(player => player.Level); //сортировка
var orderedPlayersByLevel2 = players.OrderByDescending(player => player.Level); //обратная сортировка
foreach (var player in orderedPlayersByLevel2)
{
Console.WriteLine(player.Level);
}
Нахождение минимума и максимума
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Билл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
List<int> numbers = new List<int> { 1, 5, 100, 0, 2, 1, 3, 4, 85, 9, 6, 4, 7 };
int maxNumber = numbers.Max();
int mixNumber = numbers.Min();
Console.WriteLine(maxNumber);
Console.WriteLine(mixNumber);
var maxLevelPlayer = players.Max(player => player.Level);
Console.WriteLine(maxLevelPlayer);
Проекции
При помощи select можно создать объект анонимного типа.
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Билл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
var newPlayers = from Player player in players select new { Name = player.Name, dateOfBirth = DateTime.Now };
var newPlayers2 = players.Select(player => new { Name = player.Name, dateOfBirth = "понедельник" });
foreach (var player in newPlayers)
{
Console.WriteLine(player.Name + " " + player.dateOfBirth);
}
Объединение коллекций
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Билл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
List<Player> players2 = new List<Player>
{
new Player("Майкл", 300),
new Player("Джордан", 120)
};
var unitedTeam = players.Union(players2);
foreach (var player in unitedTeam)
{
Console.WriteLine(player.Name);
}
Take и Skip
Take — взять
Skip — пропустить
Напр, при выборке игроков мы хотим пропустить один элемент:
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Билл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
var filteredPlayers = players.Skip(1); //пропустится Джон
var filteredPlayers2 = players.Take(1); //берётся только Джон
foreach (var player in filteredPlayers2)
{
Console.WriteLine(player.Name);
}
Если Take(6) будет больше кол-ва элементов — берётся всё.
SkipWhile и TakeWhile — пропускают или берут, пока выполняется условие (до 1го отличия).
List<Player> players = new List<Player>
{
new Player("Джон", 100),
new Player("Дилл", 220),
new Player("Дерек", 260),
new Player("Klark", 241)
};
var filteredPlayers = players.Skip(1);
var filteredPlayers2 = players.Take(1);
var filteredPlayers3 = players.SkipWhile(player => player.Name.ToUpper().StartsWith("Д")); //пропускает до первого отличия. Берётся только Klark.
foreach (var player in filteredPlayers3)
{
Console.WriteLine(player.Name);
}
Из IEnumerable в коллекцию
var filteredPlayers3 = players.SkipWhile(player => player.Name.ToUpper().StartsWith("Д")).ToList();
Чтобы преобразовать IEnumerable в конкретный тип используют:
.ToArray — массив
.ToDictionary — словарь
.ToList — список