Andriy Shyrokoryadov

.Net developer, data scientist

Работа над ошибками / ответы на комментарии - собеседование C# / .NET

Текст к видео "Работа над ошибками / ответы на комментарии" на канале YouTube

Данное видео является одним из последних видео на тему собеседования по языку программирования C#. Так как никто не идеален, в том числе и я, то во время съёмок некоторых предыдущих видео я допускал ошибки и неточности. На эти недочеты мне указывали мои зрители в комментариях. Это видео будет своеобразной работой над ошибкой. Я пройдусь по всем комментариям, где были замечания к моим видео и исправлю мои ошибки.

Видео о модификаторах доступа. Да, действительно – модификаторов не 5, а не 6. Когда я готовил материал к данному видео то возможно такой модификатор доступ еще не существовал в природе – идея снять серию видео о языке C# с точки зрения собеседования у меня появилась очень давно. Однако факт остается фактом – если кто-то снимает видео о модификаторах доступа в языке C# то необходимо сказать пару слов о модификаторе protected private. Данный модификатор доступа ограничивает видимость члена к области текущего класса и классов, которые наследуют текущий класс. Данное свойство доступно начиная с версии C# 7.2. Из вышесказанного можно сделать вывод, что член класса, обозначенный модификатором доступа protected private, не будет видим вне класса – он доступен только к текущем классе или его наследниках.

К видео о разнице между абстрактными классами и интерфейсами один из моих зрителей добавил достаточно обширный комментарий на данную тему. Я выделил желтым цветом ту часть, которая меня наиболее заинтересовала. В этой части есть 2 момента: упоминание Dependency Injection то есть «Внедрение зависимости» и второй момент – перечень некоторых отличий абстрактного класса и интерфейса. Тема Dependency Injection очень важна, и она возникает на собеседовании достаточно часто, поэтому я уже давно вынашиваю идею о том, чтоб снять видео на эту тему и показать, как это работает на практике. Если данная тема Вам также интересна и если Вы не подписаны на канал, рекомендую поставить видео на паузу и подписаться, чтобы не пропустить это видео. Касательно второго момента – всё что пишет мой зритель правильно, однако это можно сократить до минимума. Просто необходимо запомнить 2 факта. Факт первый - в интерфейсах до версии C# 8.0 можно было декларировать 4 типа членов: методы, свойства, индексеры, события. Все остальные типы членов, которые могут быть добавлены в абстрактный класс, не могут использоваться в интерфейсах в C# до версии 8.0. Факт второй - начиная с версии C# 8.0, как заметил зритель, в интерфейсах можно декларировать реализации по умолчанию. И тут должен был идти материал по этой теме, которого бы хватило на отдельное видео. Просто скажу, что реализации по умолчанию в интерфейсах имеют свои преимущества и недостатки. А также если Вы придете на работу не в новый проект, а в компанию со зрелым продуктом, который развивался на протяжении несколько лет, то скорей всего версии C# 8.0 вы не увидите. Будет какая-то более ранняя версия и много legacy кода.

В видео на тему цикла foreach мне сделали замечание, что данная тематика не была раскрыта полностью, что не хватало информации на тему шаблона проектирования итератор. Мне кажется, я исправил эту ошибку добавив видео на тему ключевого слово yield. Также в видео о различных типах циклов было замечание по поводу цикла foreach. Речь идет о том, что я сказал, что коллекция – контейнер, который будет использоваться в цикле foreach, должен реализовывать интерефейс IEnumerable. Будет достаточно если контейнер будет имплементировать метод IEnumerator GetEnumerator(). Более того метод GetEnumerator() может возвращать не реализацию интерфейса IEnumerator, а класс который содержит методы из интерфейса IEnumerator без явного указания на то, что данный класс реализует данный интерфейс. Если что-то крякает как утка, плавает как утка и выглядит как утка, то, наверное, это что-то является уткой. Это происходит в следствии того, что на основании выражения foreach компилятор генерирует определённый код. Ниже пример кода, написанного программистом и код, который был сгенерирован компилятором на этой основе.

Код программиста:

var cars = new List<Car>();
// добавление объектов типа Car
foreach (Car car in cars)
{
// операции над объектом типа Car
}

Код компилятора:

List<Person>.Enumerator e = cars.GetEnumerator();
try
{
	Car v;
	while(e.MoveNext())
	{
		v = e.Current;
	}
}
finally
{
	System.IDisposable d = e as System IDisposable;
	if (d != null) d.Dispose();
}

В видео о делегатах я рассказывал о предопределенных делегатах Action и Func. Один из зрителей верно заметил, что стоило бы сказать пару слов о предопределенном делегате Predicate. Да, есть и такой делегат – формально он записывается следующим образом:

public delegate bool Predicate<in T>(T obj);

Его смысл заключается в следующем – на основании объекта типа Т мы должны принять решение и возвратить true или false. Например, проходит данный объект валидацию или нет. Если всё свойства объекта заполнены правильно, то возвращаем значение true, если что-то не так, то возвращаем false.

Также предикаты полезны при фильтровании – на основании какого-то условия мы отсеиваем объекты, выбирая только те объекты, которые подпадают под данное условие.

Пример предиката:

class Person
{
public string FirstName {get; set;}
public string LastName {get; set;}
}

//predicate
bool IsValid (Person person) => !string.IsNullOrEmpty(person.FirstName) && !string.IsNullOrEmpty(person.LastName);

При объяснении полиморфизма я допустил неточность, сказав, что морф – это в переводе с греческого «жизнь». Меня поправил зритель и я проверил эту информацию. Действительно, «морф» — это не жизнь по-гречески, а «вид» или «форма», что в принципе не искажает то, что уже было мной сказано в видео. Однако зритель тоже был не совсем прав, написав, что жизнь – это «био». По-гречески «жизнь» — это «биос», а что такое «био» то, наверное, это что-то связанное с жизнью. Какое-нибудь прилагательное или еще что-то в этом духе. В любом случае это канал не филологический, и мы слегка отошли от темы.

В видео о многопоточности и способах создания потоков мной не было указано в каких именно потоках выполняется код в объектах Task. В этом видео я добавляю это уточнение. Если у Вас есть выбор что использовать: Thread, ThreadPool или Task, то конечно лучше использовать Task. О разнице между Thread и ThreadPool я по моему говорил в видео, а что касается ThreadPool и Task, то между ними разница следующая: при использовании Task Вы всегда знаете когда выполнение задания закончено, а при использовании ThreadPool нет встроенного способа определить закончено ли задание и какое значение было возвращено. Собственно этот недостаток ThreadPool послужил причиной появления концепции Task. В .Net есть такое понятие, как TaskScheduler - он отвечает за старт заданий и управляет заданиями в процессе их работы. TaskScheduler использует потоки из ThreadPool. То есть “под капотом” Вы и так используете ThreadPool, но теперь у Вас есть доступ к статусу задания (завершено/не завершено) и если задание завершено, то есть доступ к его результату.

Видео на тему .Net Core получилось неактуальным в момент своего выхода, так как в видео указывается что последняя версия .Net Core это 3.1, а по факту это .Net 5.0, который объединяет в себе .Net Core и .Net Framework. .NET 5 — это непосредственный наследник .Net Core 3.1. Материалы к видео готовились и готовятся очень заблаговременно, а также сами видео выходят с определенным временным лагом. На момент подготовки материалов .Net Core 3.1 был последней версией, но сейчас эта информация, увы и ах, уже не актуальна и требует исправления.

По моему видео на тему Garbage Collector получилось не плохим. Мне удалось построить это видео таким образом, как будто у нас был список вопросов на собеседование, связанный только лишь с Garbage Collector. Опять-таки нет ничего идеального, и в этом, казалось бы, неплохом объяснении я упустил один момент. Однако бдительные зрители сразу обратили на это внимание. Речь идет о large object heap (LOH). Garbage Collector делит все объекты на большие и малые. Большие объекты считаются более важными по сравнению с малыми. Большим объектом считается объект, чей размер превышает 85кБ. Если у нас есть объект такого размера или больше, то он будет размещен на куче больших объектов (large object heap). Когда загружается CLR, Garbage Collector резервирует 2 кучи: SOH и LOH. Малые объекты идут на кучу SOH, а большие на LOH.

Как уже было сказано в видео на тему Garbage Collector, Garbage Collector имеет 3 генерации: 0, 1 и 2. Малые объекты всегда попадают в 0-ую генерацию и в зависимости от ситуации они могут быть перенесены в первую, а затем во вторую генерацию со временем. Большие объекты всегда попадают во вторую генерацию. Когда удаляются объекты из второй генерации – это означает, что объекты нулевой и первой генерации уже были удалены.

Мне кажется, что я прошелся по всем комментариям к моим видео, которые требовали ответа. Из данной ситуации можно сделать 2 вывода. Первой – я читаю комментарии зрителей. Второй – по-моему один из стародавних философов это уже сказал: «Если хочешь узнать, что ты не знаешь, попробуй кого-то научить чему-либо». Благодаря Вашим комментариям я узнал о недостатках в моей работе и постарался эти недостатки исправить в данном видео. Естественно, когда речь идет о комментариях, то большая часть комментариев это слова благодарности. Это слова благодарности, которые меня очень мотивируют. Бывает, что мне не хочется снимать видео или заниматься монтажом, но стоит мне перечитать комментария и я вновь полон сил и мотивации продолжать мою работу.