Изучаю C#. Конспект.

За основу взят ролик 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

Схема блока 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го измерения.

Двумерные массивы

Их можно представить в виде таблицы или матрицы. Каждый элемент массива имеет два индекса.

  1. Номер строки
  2. Номер столбца

(нумерация с 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

Как преобразовывать типы без ошибок, которые не видно в редакторе, но при этом всплывают при работе проги?

  1. Использование 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.

  1. Использование 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 — список

Добавить комментарий