Я вам так скажу, если человек работает в отрасли более 3-х лет, но не может объяснить, чем в C# отличаются:
int x1 = 42;
int? x2 = 42;
var x3 = 42;
object x4 = 42;
То скорее всего он просто на***дел в резюме. Вписал туда проекты и задачи которые возможно решали его коллеги. Именно за этим и даются задания на собеседовании.
Мне в linkedin несколько раз пытался добавиться чувак, с которым я когда-то работал. Он был на позиции middle, но с трудом до неё дотягивал и был скорее средним между junior и middle. Но в linkedin он, о чудо, senior!
Когда на "неудобный" вопрос соискатель отвечает односложно и ждёт следующего вопроса, это может восприниматься как "от**итесь от меня" и это воспринимается как токсичность.
Или если человек спокойно и аргументировано объяснит свою позицию это будет восприниматься совершенно иначе.
Допустим пример: вам дают листочек и говорят: "напиши сортировку пузырьком по памяти".
Можно ответить: "Вы сами то часто код на листочке пишите? Какое практическое значение сортировка пузырьком вообще имеет? Вы что, часто её используете?"
Или можно сказать: "Я не думаю что писать код на листочке у меня получится, редко приходится таким заниматься. Могу на листочке схематично нарисовать и объяснить как это сортировка работает. Если дадите ноут, могу в IDE попробовать накидать алгоритм. Устроит такой вариант?"
Скорее всего первый ответ будет воспринят негативно, а второй более позитивно.
Ага, щас. Человек пришел к вам по сути просить денег и выполнять задания, и будь он хоть трижды позитивный и общительный это его уже подсознательно ставит в подчиненную позицию.
Если человек придёт, как вы говорите "просить денег", то значит он через месяц может уйти, потому что ему в другом месте предложат больше. Не удивительно, что такие люди не получают офферы.
В моём понимании разработчик приходя на собеседование с одной стороны должен уметь продать свои знания и опыт (и да, это навык, который ничего общего с реальной разработкой не имеет), но с другой стороны работодатель также должен продать этому разработчику свой проект, заинтересовать, показать, что человек при должной инициативе может расти в компании.
Поэтому я не согласен, что соискатель тут в какой-то подчинённой позиции, только если он себя сам не ставит в такое положение.
Тривиальные вопросы типа "на джуна" очень часто нужны, чтобы понять общий уровень. Я не одного сеньора отправил с собеседования, потому что люди не знали, чем в C# отличаются разные способы объявить переменные. (Предполагаю что в крупные компании люди прут на собеседования, авось проскачу)
Вопросы без практической пользы, типа "(true + true * 5) - 2" нужны не для того, чтобы вы на них посмотрели и дали ответ. Скорее всего от вас хотят увидеть умение думать и рассуждать, покажите, что вы это умеете.
Я часто даю на собеседование задание на "написать код", только даю что-то нетривиальное, что по памяти очень сложно написать. При этом сразу говорю, что можно гуглить, главное решить задачу. При этом на самом деле мне важен не результат, а важно увидеть как человек думает, насколько переусложняет решение, может ли он сформулировать вопрос и найти ответ в интернете.
Есть технические вопросы на которые многие кандидаты прям на собеседовании реагируют типа "что за херню вы меня спрашиваете, это не имеет практической пользы". Это признак того, что в реальных задачах человек также будет реагировать на задачи типа "что за фигню вы меня заставляете делать, я хочу задачи поинтереснее". Естественно, что такие кандидаты получают отказ, т.к. они потенциально конфликтны.
С точки зрения собеседуемого:
Подходите к собеседованию не как к тесту, а как к дружеской беседе. На позитиве. Компания смотрит на вас, но и вы смотрите на компанию. Всё должно быть взаимно.
Если понимаете, что на собеседовании вас спрашивают дичь - прямо об этом скажите и попытайтесь объяснить спокойно свою позицию. Предложите тем, кто вас собеседует техническую тему которая на ваш взгляд лучше показывает ваши технические навыки. Это действительно работает иногда. Если это не помогает, просто отнеситесь к этому как дополнительному опыту. Но таких компаний единицы.
Если что-то не знаете - прямо об этом скажите. Попытайтесь порассуждать, сделать предположение об ответе, пусть даже он будет неправильный. Главное не молчите. Это не экзамен.
С другой стороны, многие не зная ответ пытаются заговаривать зубы и без остановки несут какой-то бессвязный бред без конкретики. Так тоже не надо делать.
В случае удалённых собеседований всегда общайтесь с камерой по умолчанию. Если ваш собеседник без камеры, попросите её включить (а лучше заранее договориться об этом). Никто не захочет брать на работу аватарку, да и вам не очень захочется потом работать с людьми, которых вы в глаза не видели.
С одной стороны, это скорее дело вкуса. Многие задачи можно решить как с помощью Expression Trees, так и с помощью DynamicMethod. Я, например, уже настолько привык к последним, что код с il.Emit(...) кажется более простым и понятным, особенно если нормально его написать. Но для кого-то скомпилировать лямбду будет проще, чтобы не сильно разбираться с IL и просто сделать, чтобы работало.
С другой стороны есть несколько причин, когда Expression Trees могут и не подойти:
Поскольку Expression Trees внутри себя всё равно используют DynamicMethod, то это всё таки более высокоуровневая штука, которая может не всё уметь. По крайней мере раньше нельзя было нормально сделать цикл, ветвление, объявление переменных (сейчас возможно что-то уже появилось).
Только DynamicMethod поддерживает режим skipVisibility, когда игнорируется приватность членов. Это часто используется во всяких мапперах (тот же EF это использует, насколько я помню).
Expression Trees, насколько я знаю, нельзя привязать к объекту, как DynamicMethod. Т.е. нельзя сделать делегат с состоянием.
Да, асинхронные методы сложно будет сгенерировать динамически, т.к. нужно сгенерировать код стейт-машины. Я бы в этом случае смотрел в сторону того, чтобы код с async-await написать на C# с минимальным количеством логики, а из него вызывать синхронные динамические методы.
По поводу source generators. Я бы сказал, что у них по отношению к динамическим методам разные области применения.
Source generators подходят для генерации кода на основе исходников. Например вы разметили код какими-то атрибутами и по ним сгенерировали код на этапе компиляции.
Динамические методы больше подходят для ситуаций, когда вы на этапе компиляции не знаете, что нужно сгенерировать. Например, у вас в БД лежит какой-нибудь шаблон емейла или формула, которая вводится пользователями в интерфейсе. И этот шаблон или формула вызывается большое количество раз. Для оптимизации тут можно применить динамический метод.
Правильно ли я понимаю, что после JIT компиляции структура исполнения алгоритма остаётся в виде стековой машины?
Нет, всё компилируется в машинный код, как с обычным .NET кодом. Фактически динамические методы хранятся в динамической сборке, которая практически ничем не отличается от обычных dll сборок к которым все привыкли.
Разница только в том, что обычный код вы компилируете из C# в IL, а IL потом через JIT компилируется в машинный код. А с динамическими методами (а также с компилируемыми Expresson Trees и динамическими сборками) вы в runtime генерируете сразу IL, который для выполнения через JIT компилируется в машинный код.
За исключением, может быть, того, что динамические методы, если на них не остаётся ссылок в коде потом собираются GC.
Особенно как обрабатываются intrinsic команды (поддержка короых не так давно появилась в .NET)?
Всё что умеет оптимизировать JIT компилятор, всё будет оптимизироваться и в динамических методах.
Где размещаются аргументы функции и локальные переменные до того как они переносятся на стек или обратно?
Я не уверен, что понял вопрос. Они размещаются там же где и аргументы и локальные переменные в обычном коде.
А как идёт доступ к переменным/аргументам более высокого уровня (из локальных функций или доступ к this ссылки владельца функции)?
Локальные функции, которые обращаются к переменным из окружающего контекста - это замыкания. Для замыканий C# создаёт специальный объект, где хранит все эти переменные.
Т.е. нужно научиться в runtime генерировать точно такой же код, который сгенерировал бы компилятор C#. Это не самая тривиальная задача, но это можно сделать.
По поводу this. Я постараюсь описать это в следующей статье. При создании делегата через CreateDelegate можно указать параметр target, который хранит объект, экземпляром которого будет являться метод. В этом случае все параметры метода "сдвигаются" на единицу, а this становится самым первым параметром и его значение можно получить через ldarg.0.
Сам этот перенос - это копирование значения из одной ячейки памяти в другую (т.е. в процессе обработки переменных/аргументов они постоянно копируются)?
Нет, не копируются. Стековая машина - это абстракция, которая упрощает понимание. Любой код .NET работает через стековую машину. Потом всё это оптимизируется при компиляции в машинный код.
А как обрабатываются переменные структурных типов (структуры, кортежи.... отдельно про классы)?
Это хороший вопрос. Я постараюсь ответить на него в следующей статье =)
Я не назову вам конкретных цифр, т.к. они очень сильно зависят от того, что вы пытаетесь оптимизировать, насколько часто этот код вызывается и какую долю занимает в остальном коде. Обычно разница такая же, как если сравнивать универсальный код для решения нескольких задач и код, который написан под конкретную задачу.
Например, если это код какого-то маппера, который копирует данные из одного объекта в другой, то реализация через reflection может быть на порядок или на два медленнее.
Ещё такой вопрос, с testcontainers нет такой проблеммы, что когда останавливаешь debugger, то код финализации не выполняется и после отладки остаются висящие контейнеры?
Для меня медиатор - это в первую очередь инструмент организации кода. Да, у него есть проблемы с IntelliSense, да есть некоторый бойлерплейт, но с другой стороны решается проблема раздутых сервисов с кучей зависимостей. Тут уж кому как больше нравится.
И в этом месте в случае больших проектов с кучей логики я не вижу вообще никакой проблемы вызывать одни обработчики из других, главное, чтобы они в проекте лежали рядом с реквестом, чтобы их просто было найти.
А то, что, приводится в примере вообще больше похоже на нотификацию, которые в медиаторе тоже обрабатываются через обработчики и обычно вызываются как раз таки из других обработчиков.
Модерацией я заниматься точно не хочу и публикуемые статьи не просматриваю. Цель была скорее в том, чтобы собрать то, на что точно стоит обратить внимание, и делать это автоматически.
Если есть какой-то ресурс, который стоит добавить, его можно скинуть в issues на GitHub, я периодически добавляю новые источники. Но действительно не везде это можно делать без модерации. (На том же dev.to была куча статей на испанском)
По поводу других языков, да, была мысль сделать аналоги для DevOps и Forontend, но вряд ли руки до этого дойдут в ближайшее время.
Вы слишком много думаете о ситуации "если например в коде мы захотим переименовать".
Если у вас в БД колонка называется так, то и в коде называете её так же. Если хотите переименовать - переименовывайте и там, и там. Не заставляйте людей, которые будут читать ваш код гадать, как сопоставить свойства класса с колонками в БД.
В такой ситуации MatchNamesWithUnderscores прекрасно работает и никакие nameof не нужны. (такие запросы не читабельные совсем).
Спасибо за коммент)) Приятно, что даже спустя почти три года это кому-то пригодилось))
На самом деле позже я сделал тоже самое через vagrant и это работает сильно лучше, исходники есть тут, можно глянуть: https://github.com/Chakrygin/vagrant-kubernetes
Проблему с потерей связи я точно решил в этих скриптах, если я ничего не путаю, проблема была в том, что при установке flannel нужно там заменить IP и указать сетевой интерфейс, который надо использовать, как-то так: https://github.com/Chakrygin/vagrant-kubernetes/blob/main/provision.sh#L127-L131
Я вам так скажу, если человек работает в отрасли более 3-х лет, но не может объяснить, чем в C# отличаются:
То скорее всего он просто на***дел в резюме. Вписал туда проекты и задачи которые возможно решали его коллеги. Именно за этим и даются задания на собеседовании.
Мне в linkedin несколько раз пытался добавиться чувак, с которым я когда-то работал. Он был на позиции middle, но с трудом до неё дотягивал и был скорее средним между junior и middle. Но в linkedin он, о чудо, senior!
И я вовсе не рекрутёр, а разработчик, который частенько находится как с одной, так и с другой стороны стола на собеседованиях.
Есть большая разница
Когда на "неудобный" вопрос соискатель отвечает односложно и ждёт следующего вопроса, это может восприниматься как "от**итесь от меня" и это воспринимается как токсичность.
Или если человек спокойно и аргументировано объяснит свою позицию это будет восприниматься совершенно иначе.
Допустим пример: вам дают листочек и говорят: "напиши сортировку пузырьком по памяти".
Можно ответить: "Вы сами то часто код на листочке пишите? Какое практическое значение сортировка пузырьком вообще имеет? Вы что, часто её используете?"
Или можно сказать: "Я не думаю что писать код на листочке у меня получится, редко приходится таким заниматься. Могу на листочке схематично нарисовать и объяснить как это сортировка работает. Если дадите ноут, могу в IDE попробовать накидать алгоритм. Устроит такой вариант?"
Скорее всего первый ответ будет воспринят негативно, а второй более позитивно.
Если человек придёт, как вы говорите "просить денег", то значит он через месяц может уйти, потому что ему в другом месте предложат больше. Не удивительно, что такие люди не получают офферы.
В моём понимании разработчик приходя на собеседование с одной стороны должен уметь продать свои знания и опыт (и да, это навык, который ничего общего с реальной разработкой не имеет), но с другой стороны работодатель также должен продать этому разработчику свой проект, заинтересовать, показать, что человек при должной инициативе может расти в компании.
Поэтому я не согласен, что соискатель тут в какой-то подчинённой позиции, только если он себя сам не ставит в такое положение.
С точки зрения собеседующего:
Тривиальные вопросы типа "на джуна" очень часто нужны, чтобы понять общий уровень. Я не одного сеньора отправил с собеседования, потому что люди не знали, чем в C# отличаются разные способы объявить переменные. (Предполагаю что в крупные компании люди прут на собеседования, авось проскачу)
Вопросы без практической пользы, типа "(true + true * 5) - 2" нужны не для того, чтобы вы на них посмотрели и дали ответ. Скорее всего от вас хотят увидеть умение думать и рассуждать, покажите, что вы это умеете.
Я часто даю на собеседование задание на "написать код", только даю что-то нетривиальное, что по памяти очень сложно написать. При этом сразу говорю, что можно гуглить, главное решить задачу. При этом на самом деле мне важен не результат, а важно увидеть как человек думает, насколько переусложняет решение, может ли он сформулировать вопрос и найти ответ в интернете.
Есть технические вопросы на которые многие кандидаты прям на собеседовании реагируют типа "что за херню вы меня спрашиваете, это не имеет практической пользы". Это признак того, что в реальных задачах человек также будет реагировать на задачи типа "что за фигню вы меня заставляете делать, я хочу задачи поинтереснее". Естественно, что такие кандидаты получают отказ, т.к. они потенциально конфликтны.
С точки зрения собеседуемого:
Подходите к собеседованию не как к тесту, а как к дружеской беседе. На позитиве. Компания смотрит на вас, но и вы смотрите на компанию. Всё должно быть взаимно.
Если понимаете, что на собеседовании вас спрашивают дичь - прямо об этом скажите и попытайтесь объяснить спокойно свою позицию. Предложите тем, кто вас собеседует техническую тему которая на ваш взгляд лучше показывает ваши технические навыки. Это действительно работает иногда. Если это не помогает, просто отнеситесь к этому как дополнительному опыту. Но таких компаний единицы.
Если что-то не знаете - прямо об этом скажите. Попытайтесь порассуждать, сделать предположение об ответе, пусть даже он будет неправильный. Главное не молчите. Это не экзамен.
С другой стороны, многие не зная ответ пытаются заговаривать зубы и без остановки несут какой-то бессвязный бред без конкретики. Так тоже не надо делать.
В случае удалённых собеседований всегда общайтесь с камерой по умолчанию. Если ваш собеседник без камеры, попросите её включить (а лучше заранее договориться об этом). Никто не захочет брать на работу аватарку, да и вам не очень захочется потом работать с людьми, которых вы в глаза не видели.
Круто! Это действительно работает, я не знал, что Expression Trees так умеют.
Я ради интереса проверил, что за код там генерится. Там что-то вроде такого:
Я так понимаю, что он все объекты из
Expression.Constant
хранит в каком-то массиве, откуда их достаёт по индексу и кастит к нужному типу.На том же динамическом методе это можно сделать проще:
Тут уже вопрос в том, насколько вы хотите и любите упарываться с оптимизацией =)
По поводу листинга и картинок. Скажем так, это эксперимент =)
Я знаю, что листинги можно вставить как текст, но решил намеренно от этого отказаться, т.к. подумал, что:
скрин из IDE с привычной расцветкой синтаксиса будет лучше читаться и восприниматься;
Можно сразу показать работу кода в дебаггере с результатом.
Но я учту это на будущее.
С одной стороны, это скорее дело вкуса. Многие задачи можно решить как с помощью Expression Trees, так и с помощью DynamicMethod. Я, например, уже настолько привык к последним, что код с
il.Emit(...)
кажется более простым и понятным, особенно если нормально его написать. Но для кого-то скомпилировать лямбду будет проще, чтобы не сильно разбираться с IL и просто сделать, чтобы работало.С другой стороны есть несколько причин, когда Expression Trees могут и не подойти:
Поскольку Expression Trees внутри себя всё равно используют DynamicMethod, то это всё таки более высокоуровневая штука, которая может не всё уметь. По крайней мере раньше нельзя было нормально сделать цикл, ветвление, объявление переменных (сейчас возможно что-то уже появилось).
Только DynamicMethod поддерживает режим skipVisibility, когда игнорируется приватность членов. Это часто используется во всяких мапперах (тот же EF это использует, насколько я помню).
Expression Trees, насколько я знаю, нельзя привязать к объекту, как DynamicMethod. Т.е. нельзя сделать делегат с состоянием.
Я может идею не уловил. В чём проблема с
Delegate.DynamicInvoke
?Да, всё должно быть именно так.
Да, асинхронные методы сложно будет сгенерировать динамически, т.к. нужно сгенерировать код стейт-машины. Я бы в этом случае смотрел в сторону того, чтобы код с async-await написать на C# с минимальным количеством логики, а из него вызывать синхронные динамические методы.
По поводу source generators. Я бы сказал, что у них по отношению к динамическим методам разные области применения.
Source generators подходят для генерации кода на основе исходников. Например вы разметили код какими-то атрибутами и по ним сгенерировали код на этапе компиляции.
Динамические методы больше подходят для ситуаций, когда вы на этапе компиляции не знаете, что нужно сгенерировать. Например, у вас в БД лежит какой-нибудь шаблон емейла или формула, которая вводится пользователями в интерфейсе. И этот шаблон или формула вызывается большое количество раз. Для оптимизации тут можно применить динамический метод.
Нет, всё компилируется в машинный код, как с обычным .NET кодом. Фактически динамические методы хранятся в динамической сборке, которая практически ничем не отличается от обычных dll сборок к которым все привыкли.
Разница только в том, что обычный код вы компилируете из C# в IL, а IL потом через JIT компилируется в машинный код. А с динамическими методами (а также с компилируемыми Expresson Trees и динамическими сборками) вы в runtime генерируете сразу IL, который для выполнения через JIT компилируется в машинный код.
За исключением, может быть, того, что динамические методы, если на них не остаётся ссылок в коде потом собираются GC.
Всё что умеет оптимизировать JIT компилятор, всё будет оптимизироваться и в динамических методах.
Я не уверен, что понял вопрос. Они размещаются там же где и аргументы и локальные переменные в обычном коде.
Локальные функции, которые обращаются к переменным из окружающего контекста - это замыкания. Для замыканий C# создаёт специальный объект, где хранит все эти переменные.
Т.е. нужно научиться в runtime генерировать точно такой же код, который сгенерировал бы компилятор C#. Это не самая тривиальная задача, но это можно сделать.
По поводу this. Я постараюсь описать это в следующей статье. При создании делегата через CreateDelegate можно указать параметр target, который хранит объект, экземпляром которого будет являться метод. В этом случае все параметры метода "сдвигаются" на единицу, а this становится самым первым параметром и его значение можно получить через ldarg.0.
Нет, не копируются. Стековая машина - это абстракция, которая упрощает понимание. Любой код .NET работает через стековую машину. Потом всё это оптимизируется при компиляции в машинный код.
Это хороший вопрос. Я постараюсь ответить на него в следующей статье =)
Я не назову вам конкретных цифр, т.к. они очень сильно зависят от того, что вы пытаетесь оптимизировать, насколько часто этот код вызывается и какую долю занимает в остальном коде. Обычно разница такая же, как если сравнивать универсальный код для решения нескольких задач и код, который написан под конкретную задачу.
Например, если это код какого-то маппера, который копирует данные из одного объекта в другой, то реализация через reflection может быть на порядок или на два медленнее.
Тут приводятся некоторые цифры по поводу использования reflection: https://stackoverflow.com/questions/25458/how-costly-is-net-reflection
Естественно, нет никакого смысла писать динамический метод, чтобы вызвать его один раз на старте приложения.
И в-четвёртых, что то, что ты напишешь сегодня через несколько месяцев другой разработчик назовёт legacy, скажет что написано криво и надо переписать.
Ещё такой вопрос, с testcontainers нет такой проблеммы, что когда останавливаешь debugger, то код финализации не выполняется и после отладки остаются висящие контейнеры?
За testcontainers - спасибо. Видел какой-то аналогичный проект, но этот кажется симпатичнее.
По поводу запуска тестов в CI - в github actions есть services, которые кажутся более правильным подходом (в gitlab тоже есть аналог) - https://docs.github.com/en/actions/using-containerized-services/about-service-containers
Я категорически не соглашусь с первым пунктом.
Для меня медиатор - это в первую очередь инструмент организации кода. Да, у него есть проблемы с IntelliSense, да есть некоторый бойлерплейт, но с другой стороны решается проблема раздутых сервисов с кучей зависимостей. Тут уж кому как больше нравится.
И в этом месте в случае больших проектов с кучей логики я не вижу вообще никакой проблемы вызывать одни обработчики из других, главное, чтобы они в проекте лежали рядом с реквестом, чтобы их просто было найти.
А то, что, приводится в примере вообще больше похоже на нотификацию, которые в медиаторе тоже обрабатываются через обработчики и обычно вызываются как раз таки из других обработчиков.
Спасибо за предложения!
Модерацией я заниматься точно не хочу и публикуемые статьи не просматриваю. Цель была скорее в том, чтобы собрать то, на что точно стоит обратить внимание, и делать это автоматически.
Если есть какой-то ресурс, который стоит добавить, его можно скинуть в issues на GitHub, я периодически добавляю новые источники. Но действительно не везде это можно делать без модерации. (На том же dev.to была куча статей на испанском)
По поводу других языков, да, была мысль сделать аналоги для DevOps и Forontend, но вряд ли руки до этого дойдут в ближайшее время.
Вы слишком много думаете о ситуации "если например в коде мы захотим переименовать".
Если у вас в БД колонка называется так, то и в коде называете её так же. Если хотите переименовать - переименовывайте и там, и там. Не заставляйте людей, которые будут читать ваш код гадать, как сопоставить свойства класса с колонками в БД.
В такой ситуации MatchNamesWithUnderscores прекрасно работает и никакие nameof не нужны. (такие запросы не читабельные совсем).