Изучаю 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

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("Нет такого дня.");
}


Массивы

Одномерные массивы

Набор последовательных ячеек. В каждой ячейке лежит своё значение. К каждой ячейке можно получить доступ по её номеру.

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();
}


Коллекции

Коллекции — программный объект, кот. содержит в себе набор значений одного или нескольких типов.

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
{

}

Поля и модификаторы доступа

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 класс

При описании типов можно исп структуру. Структура — сходное с классами средство, кот отличается семантикой.
Объект класса — сущность. И он не равен другому объекту, даже если все поля этих объектов имеют одинаковые значения.
Объект структуры — это объект значения. И он равен другому объекту, если значения полей совпадают.
Пример: класс — два пользователя с одинаковыми фио, и структура — две точки в пространстве с одинаковыми координатами.

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