Comments 62
#1 Мантра про 3 поколения в любой ситуации
По-моему это почти всегда вопрос чисто для собеседования. Мне кажется, потому что Рихтера дальше сборщика мусора большинство не осилили.
В C# очень мало способов работать со сборщиком мусора и реально редки кейсы, когда знание про то, что он есть хоть как-то помогает. Другими словами, раньше ваш проект закроют, чем вы дойдёте до тонкой оптимизации кода под сборщик мусора.
UI'ный поток
Специфика конкретного UI-фреймворка. Следуйте гайдлайнам и не будет таких проблем. Понятно, что у разных UI-фреймворков свои тонкости.
Я никогда не работал с WPF и Unity, а в asp.net таких проблем не было и нет, там проблемы свои, но я за 10 лет ни разу не встречался с проблемами от GC в самых разных проектах.
Сейчас с async-await стало чуть легче писать подобный код.
Многие фрэймворки требуют обращения к UI'ным объектам только в UI'ном потоке, просто потому что STAThread — тяжкое наследие COM'а.В iOS изначально можно было обращаться к UI из любого потока. Правда если так делать, то потом получаешь такие состояния UI, о которых даже подумать не мог.
Среди зеленых юниоров распространено мракобесие по поводу очевидных (только что прочитавшему книгу про память) вещей
Зачем вообще спрашивать джуна про количество поколений CG?
Чтобы определить интерес к программированию надо смотреть как и что человек рассказывает про прошлую работу/опыт/проекты/дипломы и как он рассказывает про то, чем хотел бы заниматься. Для этого интервьюиру нужно иметь богатый опыт разработки и кругозор в технологиях, коим большая часть 23-летних тимлидов, разумеется, не обладает.
А вот и нет. Врать про прошлые проекты, где в реальности на совещаниях сидел да одну фичу годами точил может (почти) каждый. А спросишь его элементарный синтаксис — всё поплыл. Вы удивитесь, но примерно 80% на вопрос о garbage collector или GetHashCode не отвечают вообще ничего.
Кровавый энтерпрайз за дофига зарплаты.
А вы точно специалист?
Стек вызовов — структура данных, а именно стек, для хранения адресов возврата, для возвращения из функций. Стек вызовов — понятие больше логическое. Оно не регламентирует где и как должна храниться информация для возврата. Получается, что стек вызовов — самый обычный и родной нам стек т.е. Stack TКаждое предложение, на мой взгляд, ложное.
Стек поддерживаемый CPU хранит еще значения регистров до изменения этих регистров новой функцией.
Может до JIT оно больше логическое, но вряд ли, но после JIT наверняка используется машино-зависимый стек поддерживаемый CPU, то есть точно не Stack T, то есть именно регламентируется где и как должна хранится информация для возврата, что бы CPU это понял.
Конечно если Stack T — вдруг стал виртуальной оберткой над поддерживаемым CPU стеком, то конечно это могло бы быть правдой, но нет.
Иными словами, адрес возврата всё равно хранится в стеке, просто конкретное место хранения в кадре определяется не архитектурой процессора, а компилятором.
Да и не для начинающих часть из пунктов интересна только в определённых ситуациях/проектах. И в этих случаях на мой взгляд всё равно надо регулярно поглядывать не меняется ли ситуация с новыми версиями и/или библиотеками
человеку, только начинающему писать на С#
это не джун, это школьник/студент. Максимальная позиция — интерн. Джун — это человек, который пишет код, который потом попадёт в продакшн. И этот код должен быть предсказуемым и поддерживаемым. Когда «джун» случайно увидит в интернетах слово «рефлексия» и начнёт пихать её во все щели в проекте — тогда будут проблемы. Это основы. Это одна несчастная книга, которую можно осилить за полгода и писать код сознательно а не «скомпилилось? ничёси я крутой, коммичу»
А во вторых если взять конкретно те пункты, которые автор описал в своей статье, то они не особо нужны ни джунам, ни даже миддлам. И уж точно они менее важны чем куча других вещей, которые полезно знать людям пишущим на С# и о которых в статье нет ни слова.
Но вот пункты про IDisposable и про количество потоков — прям наболевшее. Кто-то где-то пишет using на общий ресурс, и всё, ищи потом, почему конекшен закрылся внезапно. Ну а с потоками доходило до того что ресурсов на их создание уходило на несколько порядков больше, чем на саму задачу.
Именно там это любят спрашивать и именно там кочуют неправильные ответы, которые тиражируются на всяких форумах и бложиках. Мне порой казалось, что собеседующие как раз с бложиков и понатягивали эти вопросы.
Как-то раз интервьюер мне спрашивал про разницу между значимыми и ссылочными типами. Я ему рассказал про разницу синтаксиса, про конструкторы, про ссылки на кучу, про способы передачи и упаковку… А он мне, «есть ещё одна разница»… Я чуть потупил, потом говорю про выделение на стеке и в куче. Он радостно подтвердил, что именно это хотел услышать, но я сломал ему систему — рассказал про то, что локальная переменная всегда на стеке (хотя иногда и в регистре сразу), а вот её содержимое может и в куче, если это ссылочный тип. Хуже того, если поле значимого типа часть ссылочного типа, то значение окажется сразу в куче. А если в локальной переменной значимого типа поле ссылочного типа, то значение оного будет снова в куче. А совсем худо от того, что компилятору/оптимизатору может хватить ума весь ссылочный тип уложить на стек, если он коротко живёт.
И чтобы уж совсем его добить, я спросил «Как думаете, а поле типа int в классе претерпевает boxin/unboxing»? (да, это был не первый тупой вопрос на этом собеседовании и я был уже немного злой).
Мораль: всё зло от соцсетей :)
Точнее от того, что джуны не книжки читают, а их краткие пересказы (и это касается не только программирования), не пишут экспериментальный код, а смотрят в ютубе, как это делает кто-то другой :(
Если пока ещё нет конкретной цели, но интересен C#, можно начать c официальных туториалов, или с этой подборки.
Я на .net пишу с 2002. Тогда книжек не было — я шерстил msdn, rsdn. Писал, ковырял много чего. Потом избранные главы из разных авторитетных книжек, по мере выявления потребности.
Но я вообще по книжкам учил только Basic (89-й), C по Кернигану и Ритчи (90-й), да C++ по Страуструпу (92-й). Всё остальное — хэлпы, мануалы, доки и метод научного тыка… Да, блин, я английский выучил по докам :)
Самое лучшее, пока что есть, это статьи и официальная дока. Microsoft очень по этому поводу запарились и привели её в божеский вид.
Стоит читать framework design guidelines.
… реально снотворное.
Самое лучшее, пока что есть, это статьи и официальная дока.
…
Стоит читать framework design guidelines.
Просытня советов — это разве не скучно?
Людям скучно читать что-то не про них (Сильмариллион какой-нибудь), а framework design guidelines — это именно про то с чем мы сталкиваемся каждый день.
Ну вот это например: docs.microsoft.com/en-US/dotnet/standard/design-guidelines/choosing-between-class-and-struct
чудо же.
Тоже неплохо (для того, кому нужно завтра проект поддерживать, а технологию он впервые видит и нет времени читать тысячестраничные талмуды), но я бы не сказал, что это даже сейчас может быть достойным заменителем более подробным книгам.
Как мне кажется, не столько времени уходит на само чтение книг, сколько на усвоение информации. Много взаимосвязей, которые бывают неочевидны даже после первого прочтения. Шилдт же проще потому, что у него процент воды к информации выше (и это не плохо, ибо позволяет мозгу выиграть время на усваивание) и материал проще. А Рихтер рискует стать настольной книгой, хотя бы на какое-то время, где даже после первого прочтения придется возвращаться к отдельным главам за вновь появившимися вопросами, когда очередной пласт информации уляжется.
Финализатор. По своей сути является страховкой. Вызывается неявно, в неопределенное время, во время сборки мусора.Если точнее, то GC только помещает объект в очередь финализации. Из этой очереди объекты забирает отдельный поток финализации, который и вызывает финализатор.
Также пытаться работать с диском из многих потоков — самоубийство. Диск и без того является тормозящим фактором многих программ, которые с ним работают. Если пытаться работать с ним из многих потоков, то надо забыть о быстродействии.
Для SSD проблема становится гораздо менее острой, в большинстве случаев можно не заморачиваться.
Также пытаться работать с диском из многих потоков — самоубийство. Диск и без того является тормозящим фактором многих программ, которые с ним работают. Если пытаться работать с ним из многих потоков, то надо забыть о быстродействии.
С одной стороны хорошо, а с другой — не всегда. Можно на выходе получить софт, который тормозит, потому что форматируется дискета.
Реальный пример: однопоточный SMB-сервер. У меня была проблема с умирающим хардом, который мог несколько секунд отвечать на запрос. И пока он отвечал на запрос, работа с другими дисками была невозможна. В итоге подвисали все SMB-соединения.
Однако такая имплементация пула объектов — однозначно плохая идея. Как и пытаться логировать, кидать исключения, обращаться к базе и тысячи подобных действий.
Вообще, в финализаторе логировать часто хорошая идея. Потому что вызов финализатора свидетельствует о том, что разработчик забыл Dispose (Естественно, там не забыть GC.SuppressFinalize или вляпать флажок).
Чтобы гарантировать, что Dispose() таки вызвали, нужно тесты писать.
Я как-то из-за забытого диспоза при установке приложения клиенту, поехал не на поезд, а в гостиницу. И да, конечно же приложение не говорило, что кто-то забыл диспоз на 78-ой строчке в конкретном файле, и то, что забыт именно диспоз выяснилось потом.
Тем не менее, гарантируется что у обычных объектов финализаторы вызываются раньше чем у наследников CriticalFinalizerObject.
Что означает, в том числе, безопасность обращения к любым SafeHandle в "логирующем" финализаторе.
Ну и между SafeHandle отношения владения можно выстраивать при помощи подсчёта ссылок (DangerousAddRef/DangerousRelease), но тут уже надо внимательно всё проверять.
А логгер этот не простой, а «золотой» — использует
abstract class LoggerWrapperImpl {}
в качестве таргета для строчек лога. Это нифига не наследник SafeHandle — вполне может уже прибит в момент работы финализатора.
В реальной жизни самое важное это вообще умение декомпозировать задачи, про которое ни на одном собеседовании не спросили и не спросят, потому что оно «не про язык программирования».
Также пытаться работать с диском из многих потоков — самоубийство. Диск и без того является тормозящим фактором многих программ, которые с ним работают. Если пытаться работать с ним из многих потоков, то надо забыть о быстродействии.
Это может быть два физически разных диска, тогда одним потоком читаем, другим пишем. Это быстрее чем одним потоком оба действия.
Еще бывает и внутреннее распараллеливание потоков записи или чтения (обычно для SSD), тогда последовательная запись данных одним потоком может оказаться еще и медленнее 2-4 потоков.
В третьих у дисков иногда бывает кэш и множество коротких операций чтения/записи работают сильно быстрее в многопоточном режиме, если укладываются в размер кэша.
Если кто в курсе про файловые системы, то у каждого файла есть метаданные (имя, размер, атрибуты, время создания, изменения, доступа, и данные распределения на диске). ОС с целью обеспечения сохранности структур файловой системы все время фиксирует на диск их изменения для каждого файла. То есть один поток вынужден ждать когда ОС зафиксирует очередное изменение на диск, а много потоков могут разделять это время.
Конечно если паттерн обращений на диск не может параллелиться, а зачастую это так, то лучше это делать одним потоком.
Заблуждения начинающих C# разработчиков. Пытаемся ответить на стандартные вопросы