Приветствую, дорогие друзья, в эфире Radio.net выпуск номер 108, а в студии, как всегда, ее постоянный ведущий Анатолий Кулаков. И Игорь Лобутин, всем привет. А за нашими плечами большая толпа помогаторов. Среди них. Александр, Сергей, Владислав, Гурий Самарин, Александр Лапернин, Виктор, Руслан Нартамонов, Сергей Бензенко, Лазарев Илья, Шевченко Антон.
Ольга, я люблю стрингбилдер, Бондаренко, Сергей Краснов, Константин Ушаков, Андрей Фазлеев, Дмитрий Павлов, Пасторныков Андрей, Дмитрий Сорокин, Александр Ерыгин, Егор Сычев, Гальдебаев Александр, Тимофей. И много-много еще замечательных людей, которые пожелали остаться неизвестными. Всем вам большое спасибо, что поддерживаете подкаст. А если вдруг не поддерживаете, задумайтесь. Может, уже пора. Может, уже стоит. Итак.
Что у нас там сегодня? Microsoft все еще не проснулся. Больших новостей от него ожидать не стоит. Да, больших нету. Там есть какая-то движуха потихонечку в репозитории, но пока не настолько всего много, чтобы обращать на это внимание. Ну, в смысле, мы можем, конечно, начать разбирать индивидуальные pull-requests, но это прям будет совсем детально и, наверное, не стоит того. Лучше мы подождем какого-нибудь превью 1.
в котором что-нибудь выйдет. А еще, кстати, Aspire обещали 9.1 буквально что-то тут вот в феврале-марте. Они прямо Aspire так сделали, чтобы как раз вопреки всем релизам, всем каким-то ивентам выдавать нам инфоповоды. Ну, типа того, да. Посмотрим, как будет. Может выйдет, может не выйдет. Ну, где-то в Твиттере я такое встречал, что вот, мол, мы скоро планируем.
Пока статей хватает, давай посмотрим, что есть. Да, статей хватает, поэтому пойдем к первой статье. Первая статья у нас сегодня про object-пулы и вообще про пулы объектов в .NET. Не то чтобы сильно часто приходится ими пользоваться, но иногда попадаются кейсы, когда они нужны. И тогда, несомненно, нужно рассмотреть их использование. Давайте подумаем, посмотрим, что же это такое, зачем использовать и как оно внутри реализовано. Вообще, пул объектов — это, вообще говоря, паттерн.
который позволяет вам повторно использовать какие-то объекты вместо того, чтобы их создавать. Это может быть полезно в ситуациях, когда... Объект у вас какой-то очень тяжелый и дорогой для создания, то есть он требует какой-то огромных ресурсов ЦПУ или просто сложных каких-то расчетов для...
инициализации своей, ну, либо если у вас объекты-то создаются дешево, как у нас в датнете во многом и есть, если вы создаете объект, и у него нет конструктора, например, то создание объекта локация это очень дешево, но если вы это делаете миллионы раз за короткий промежуток времени, то это может создавать некоторую дополнительную нагрузку на garbage collector. Это может создавать такой эффект, который называется memory traffic.
когда вроде как garbage collector, конечно, удаляет все ваши свежесозданные объекты, которые живут очень коротко, но просто за счет того, что этих объектов становится очень много, время, которое затрачивается на garbage collector, становится каким-то заметным. И в этом случае для того, чтобы уменьшить memory traffic, может быть полезно использование пула. Само по себе использование, оно в общем...
Довольно тривиально, вы получаете объект оттуда, вы как-то его используете. И самое главное, вы объект обратно в пул возвращаете, не просто его выкидываете, а возвращаете, чтобы он не потерялся, не собрался гарпич-коллектором, а все-таки остался для последующего использования. Иногда...
В некоторых пулах может присутствовать логика, что при возврате объекта с ним производят некоторые дополнительные действия. Например, если вы возвращаете какой-нибудь список, то вам при возврате вы можете список очистить. Или если вы возвращаете массив, то массив.
их при возврате могут обнулить. В смысле заполнить все элементы дефолтными значениями. Но это опционально. В коде это обычно выглядит, что вы... где-то в какую-то переменную затягиваете себе объект из pullAtom, методом get, take, rent, как угодно, после чего в блоке try делайте с ним что-нибудь и в блоке finally обязательно делайте pool.return или там
Ну, наверное, кроме return я что-то не видел больше других вариантов. Обычно return, передать сюда этот объект, и, в общем, все, на этом работа закончена. Почему это важно делать в finally? Потому что объект в любом случае вернулся в pull. Ну и надо понимать, что с объектом вы можете работать только внутри этого Tri-Final. Все сохранять ссылки на такие объекты за пределами такого Tri-Final конечно не стоит, потому что в этом объекте может оказаться что угодно.
потому что этот же объект может взять какой-нибудь соседний кусочек кода и с ним тоже что-то поделать. В .NET есть два встроенных класса, которые вам доступны с точки зрения пулинга. Это ObjectPool и это ArrayPool. Рассмотрим их по очереди. ObjectPool исходно доступен в Espenet Core. Если вы используете Espenet Core, он у вас есть. Либо можно подключить Nougat пакет Microsoft Extensions в ObjectPool, тогда он у вас будет где угодно.
Для того, чтобы создать сам пул, вы сначала вызываете статический метод у класса ObjectPool, то есть ObjectPool.create, и указываете в этих угловых скобочках, какого типа объекта вы там будете хранить. После чего у пула можно вызвать метод get и получить собственно объект. И для того, чтобы обвернуть, можно сделать ретурн. Дальше вы можете реализовать специальный интерфейс IPooledObjectPolicy.
И в нем реализовать два метода, create и return, в котором вы можете производить какие-то дополнительные действия с объектами. Например, именно как раз в return вы можете объект, перед тем как он возвращается непосредственно в пул, почистить. Это делается, по сути, в итоге, ну, как бы, в инфраструктурном коде, да, и в вашем коде вы просто пишете pool.return и ссылка на объект, а за счет наличия вот этого policy...
этой полисе происходит какая-то дополнительная очистка при возвращении. Как он внутри устроен? Этот самый Object Pool. Он устроен довольно незатейливо, так скажем. У него есть всего два поля. Одно поле называется FastItem, а второе называется Items и является очередью. Как происходит получение объекта? Ну, оно тоже максимально простое. Если у нас в поле FastItem лежит не null, то мы достаем оттуда объект и его возвращаем. Естественно, поскольку пул потокобезопасный, то вы можете...
доступаться к нему из нескольких разных потоков. И для того, чтобы все это было синхронно и безопасно, используется InterlockedCompareExchange. То есть, грубо говоря, мы делаем InterlockedCompareExchange на fast-item с null. Если в ответ вернулся не null, все, отлично, можно этот объект вернуть. Если вернулся null, ну, значит, как это...
Оттуда там все занято было, там не было свободного объекта. И тогда мы берем из items. Items — это concurrent queue, просто конкурентная очередь, которая, понятно, потоку безопасна, мы оттуда просто вытаскиваем. Если и там ничего нет, то есть очередь пуста. то создаем объект фабрикой. Надо сказать, что объекты не могут накапливаться бесконечно, если вы это сделаете, у вас будет, по большому счету, может быть, утечка памяти, и поэтому обычно пул как-то ограничивается по размеру каком-то разумном.
Когда вы поработали с объектом и хотите его вернуть обратно, вы, во-первых, ну, вы просто вызываете метод return, там, соответственно, происходит вот это... проверка объекта и какие-то дополнительные действия благодаря policy. После чего, если точно так же через interlock.compare.exchange fastitem был null, то мы сохраним туда. Если же
FastItem уже не null, то есть кто-то туда успел до вас сохранить объект, ну тогда запихнем в очередь items. Но опять же, если вдруг размер очереди превысил незаданное значение, то объект просто выкинет. Ну то есть он никуда не сохранится, и его рано или поздно соберет в гармич коллектор. С точки зрения производительности в статье приводятся некоторые графики производительности, графики очень тяжело обсуждать, поэтому тут просто приведу общий вывод статьи про то, что в однопоточном сценарии...
Он позволяет увеличить скорость на 10-50% по сравнению с созданием нового объекта для каждой интерации того цикла, в котором это все тестируется. Но при многопоточном доступе понятно, что на маленьких объектах результаты хуже, хотя, опять же, размер самого объекта по большому счету может быть даже не сильно важен, потому что затраты гармич-коллектора на локейт маленького и большого объекта по большому счету одинаковые.
Если мы говорим, например, о каких-нибудь массивах просто разного размера, потому что, ну, карбач коллектору неважно, сколько аллокетить, разве что там будут небольшие затраты на аллокет новых регионов, если их вдруг почему-то не хватает. А когда ты говоришь про 10-50%, это имеется в виду объекты, которые создаются мгновенно? Пустые какие-то объекты? Ну да, там довольно синтетический тест, поэтому понятно, что...
И там в комментариях, кстати, справедливо замечено, что все эти приросты, они, конечно, надо мерить на реальных живых приложениях, просто потому что мы по факту меряем, в том числе, производительность garbage collector, по большому счету. Ну, типа, да, Apple используют, когда...
Как ты сказал, у тебя большие весомые объекты, и там твои 50% уже превратятся в 500 миллионов процентов. Да, либо у тебя очень много локейтов, и тогда действительно уже становится важно, что ты не генерируешь memory traffic на 2 гига. памяти аллогейтов на один входящий запрос в твое приложение. Вероятно, там уже пул нужен более какой-то умный, чем просто обычный Concord Kill.
Там может быть достаточен Concurrent Queue. У меня есть опыт работы с такой оптимизацией. Мы Object Pool используем. Правда, у нас был такой полусамописный, но тоже по сути на Concurrent Queue. Там все было нормально, все можно было улучшить просто за счет того, что мы переиспользовали некоторые наши внутренние объекты, которых мы знали, что нам нужно создавать.
Ну, за время работы алгоритма много, но одновременно в памяти живет их не очень много. Ну, там были некоторые такие специфические математические вычисления, и некоторые наши объекты... Ну, представь, не знаю, какие-то там комплексные числа, грубо говоря, да? Ну, там структурка из пары полей. Ну, у нас была структурка, по-моему, из пяти или шести полей, если я правильно помню. Но вот их там в процессе вычисления приходилось аллокетить много.
И там пул реально прям хорошо спасал, потому что можно было их переиспользовать фактически. А иначе на одно вычисление там действительно профайлер честно показывал 2-2,5 гига меморитрафика. У нас не было цели вложить какой-нибудь, чтобы этот HCTP метод возвращался за 100 миллисекунд. Нет, это заранее было известно, что это долгое сложное вычисление. Оно выполнялось несколько секунд.
Но все равно, если можно за счет пула из... Я сейчас навру, наверное, в числах, но, грубо говоря, из 2,5 гигабайт memory трафика мы снизили до 130 или 150, что как бы приятно. Да, тогда просто цифры, что ты на пустом поле собираешь 50% перформанса, наверное, вообще ничего не значат. Да-да-да. Ну, то есть на графике, может быть, посмотреть полезно, как, например, это меняется в зависимости от размера чего-нибудь.
Но, или от количества, или еще что-нибудь. Но тут скорее я все это рассказываю больше ради именно принципа работы, чтобы просто те, кто не знал, возможно, знают, что вот есть такая штука, как Object Pool. Потому что, как мне кажется, про вот следующую конструкцию, RayPool, про него говорят много кто. Ну, он во всяких докладах встречается, RayPool, он там, когда появился нормальный RayPool, там, про него статьи были, все такое. А Обжига Пола, он, мне кажется, довольно давно уже с нами.
И как-то его... Вот почему-то у нас он был написан свой. Ну, может быть, потому что проект начинался на эти 3,5, его там еще не было, тут я уж не знаю. Но... Да, может, не так давно. Да, да. Ну, неважно. Ладно, давай дальше. Дальше у нас есть ArrayPool. Это уже специализированный класс. Точнее, это на самом деле абстрактный базовый класс. У него есть две реализации. И он предназначен для пулинга только массивов и больше ничего.
У него есть две реализации, как я сказал. Это Shared ArrayPool и Configurable ArrayPool. Они отличаются немножко поведением и внутренним устройством. Значит, Shared ArrayPool... используется как такой единый статический синглтон на все приложение. То есть если у вас нет каких-то супер... специальных требований к тому, вы точно знаете, какого размера пул вам нужен и так далее. Я сейчас чуть дальше расскажу, как можно настраивать рейпул.
то обычно рекомендуется начать с шердера и пула. С ним, скорее всего, у вас получится уже все хорошо. И только если вам его не хватает, тогда идите в какую-нибудь кастомную настройку. Или, например, вам нужны разные пулы по каким-то причинам с разными настройками. Но если вам достаточно shared, а с него желательно начать, то вы просто говорите везде, где вам нужен pool, вы используете raypoolat.shared. И это потокобезопасный инстанс, который можно от него просто вызывать rent и return.
Точно так же в TryFinal. Если вам нужен Configurable, то вы вызываете статический метод raypool.create и туда передаете настройки. Настройки всего две, это максимальная длина массива, которую... нужно хранить в пуле. Это как раз может быть полезно, если, например, вы знаете, что вам нужен пул большого количества не очень длинных массивов, то дефолтные настройки
которые по дефолту, если я правильно помню, локитят массивы до, по-моему, 4 мегабайт или 2 мегабайт. Вот я сейчас не вспомню точно. Если вам такие большие массивы не нужны в поле... то можно настроить сколько их там будет, а еще можно настроить количество массивов на один bucket. То есть сколько, грубо говоря, каждого размера будет массивов в поле.
содержаться, а все, что больше, будут выкидываться. Если вы вдруг налогитите больше, то будет выкидываться. Размер возвращаемого массива при работе с ArrayPool. Нужно быть внимательным, потому что размер будет не тот, который вы запросили, с наибольшей вероятностью. Ну, как обычно работают. То есть у вас там, не знаю, пришел какой-нибудь запрос, вы понимаете, что там для обработки каких-нибудь данных вам нужен массив там длиной, не знаю.
3 килобайта. Вы говорите raypoolshare.rent от 3 килобайта. Вам вернется с 99% вероятностью, если не со 100% массива из 4 килобайт. Потому что длины мотивов, они фиксированы, макетов много, сильно плодить не хочется. Поэтому они, ну, грубо говоря, по степеням двойки там аллоцируются, и вам вернется... больший, возможно, массив, поэтому когда вы пишете код, надо понимать, что там array.length писать будет нельзя, нужно будет знать, сколько вы в этом массиве заполнили сами по себе.
Как устроен Shared и Ripple внутри? Они немножко по-разному устроены с Configurable. Shared, он такой довольно затейливый. Во-первых, есть для каждого потока аналог вот этого FastItem.
поля, который был в Object Pool, но для каждого размера массива у вас есть ссылка, которая хранит ваш локальный кэш для каждого потока, то есть Грубо говоря, массив массивов по количеству размеров, которые вы настроили, или которые настроены в шарпуле, то есть один массив каждого размера в каждом потоке может содержаться локально свой. И потом поверх этого есть еще общий кэш, в котором есть массив партишенов. Это, точнее, массив partitions, если быть точным.
Это, соответственно, для каждого размера свои. А каждый partitions — это массив уже partitions по числу CPU-шек. А в каждом CPU это, по сути, набор из 32 элементов максимум. Значит, это шертера и пулей, он не настраивается, он как бы зафиксирован. То есть как работает в данном случае, условно говоря, сохранение, например, при возврате.
мы пытаемся сохранить сначала в Thread Local Storage, в тот самый кэш, который локального потока. Помним, что там для каждого размера массива есть только одна ячейка, куда его можно сохранить. Дальше внезапно. Если там в этой ячейке что-то есть,
то, казалось бы, нам нужно пойти и положить его в далекое куда-нибудь хранилище. Но нет. Почему? Если мы этот массив вернули, значит, мы недавно с ним работали. И с хорошей вероятностью, если мы... прямо сейчас возьмем этот массив снова, то он же будет где-то там в кэшах процессора, вот это все, и поэтому очень желательно то, что вы сейчас возвращаете, положить в
тот самый ThreadLocalStorage. А то, что лежит в ThreadLocalStorage, переложить подальше. То есть при сохранении происходят два действия. То, что уже лежит в ThreadLocalStorage, оно убирается в дальнее хранение, а то, что вы только что сохраняете, сохраняется в TLS. Вот. То есть, сохраненный последний массив, массив, который вы последний вернули, он всегда лежит в thread local storage. Вот.
А внутри бакетов вот этих вот, которые партишены, мы находим нужный партишен для своего CPU в нужном размере и пытаемся сохранить. Вот если там нет места, ну тогда попробуем запихнуть в какой-нибудь соседний партишен для какого-нибудь другого CPU. чтобы не потерять. Если понятно, что все места заполнены, ну все, как бы объект просто соберется в garbage collector в какой-то момент. Нужно понимать, что если вы аллоцируете массивы,
то вам будет подбираться массив, грубо говоря, минимально возможного размера, который есть в наличии в пуле и который можно вам вернуть. Но вы же вольны попросить массив и большего размера. которого нет в пуле, ну или которого по какой-то причине не нашлось, то в таком случае массив будет вам залокичен, вы сможете воспользоваться, но при ретурне такой массив просто соберется в гарпич коллектора.
Поэтому важно следить, что когда вы работаете с Raypool, то у вас на самом деле не происходит вот эта вот фоновая локация, а вы реально используете массивы из пула. Я про это чуть попозже поговорю. Возвращаясь к Raypool, второй вид Raypool Lite, Configurable Raypool, там все попроще, там есть только одно поле buckets, это просто массив бакетов для каждого размера, и в каждом бакете это, собственно, массив.
того, где лежат ссылочки на уже готовые массивы в нужном количестве, которые вы задали в конфигурацию. То есть просто двумерный массив ссылок на массивы, по сути говоря. И здесь уже нет никакой там хитрой магии с Thread Local Storage, вот этим всем, CPU Specific, это все такое. Здесь используется тупо SpinLock для синхронизации, то есть считается, что надолго синхронизации не будет, и по большому счету там действительно надолго.
Никакой синхронизации не нужно. Либо есть место, либо нет места. И, соответственно, при получении массива, либо есть массив в массиве, либо нету. Ну, как бы, если нету, все, пошли, фабрику дернули, создали. С точки зрения скорости. здесь как раз, ну, тоже синтетические тесты, конечно, но было интересно, что Shattery Pool довольно шустрый, только на совсем уж маленьких массивах там
Видимо, оверхеттно вот эту всю магию синхронизации становится заметен и тогда как бы не очень эффективно. В общем, так же, как в прошлом случае. Скорость мерите в вашем приложении. И внимательно смотрите. Возвращаясь к метрикам, я сейчас не вспомню наизусть, что там в Object Pool, но Object Pool по опыту нужен все-таки достаточно редко.
потому что в большинстве случаев проблемы возникают с memory traffic, а memory traffic это, как правило, большие объекты, большие объекты в dotnet это там на 90% это либо строки, либо массивы. Строки пулить смысла нет, потому что строки у нас read-only. А вот массивы пулить смысл есть. Поэтому в большинстве случаев, конечно, вы всегда будете начинать с рейпула и пулить массивы. Так вот, в рейпуле точно есть метрики.
И метрики есть как раз таки вида, во-первых, сколько массивов отдано из пула, сколько массивов вернули в пул, и сколько массивов залокетили, потому что, может быть, две причины, потому что а. У вас закончился пул, и потому что вы просите массивы размера, которых просто нет физически в пуле. То есть у вас пул настроен, как я говорил, до 2 или до 4 мегабайт по умолчанию, а вы все время просите 10 мегабайт. Пул в таком случае бесполезен.
На эти метрики надо хотя бы в тестах обязательно смотреть, потому что вы можете думать, что вы круто что-то соптимизировали и настроили пул, хотя по факту там просто будет тупо работать нью под капотом и никакого пулинга не будет.
На это важно смотреть. И если вы видите, что Shared Rate Pool по какой-то причине не вывозит, в том плане, что... Дефолтные настройки приводят к большому количеству того, что либо у вас не хватает вечно массивов в поле, то есть вам нужен действительно какой-то большой пул, хотя это по алгоритмам пересмотреть, либо... вы там с размерами что-то, дефолтный набор размеров вас не устраивает, ну тогда смотрите в сторону Configurable ArrayPool.
Но опять же, после его внедрения, даже если вы что-то протестировали на Shareter и Poly, на Configurable надо тестировать отдельно, потому что у него другая внутренняя структура, другое устройство, другие примитивые синхронизации, он работает чуток по-другому. Вот такие вот дела про пулинг. На этом тут все. Ну, отлично. Это стандартный урок по ликбезу в .NET. Можно считать завершенным.
На этом можно приходить к другим, более практичным темам. Давай, практичная тема. Что у нас сегодня за практичность отвечает? Ну, получается, судя по темам, я, наверное, да? А как тебе часто приходилось, Аштамуэль? рендерить в PDF. Ну, или вообще в PDF-документы любые генерировать приходилось. Честно скажу, что PDF-ки генерить мне было нужно, но это было настолько в древние времена.
то, что я тебе назову технологию Crystal Reports, и ты поймешь, насколько это были древние времена. Да-да, и половина слышателей сейчас отваливаются просто, не понимая, что это и зачем. Да-да-да. А еще там был Log4Net, а еще Log4Net с Crystal Reports конфликтовали, потому что у них был одинаковый пабликей. Помнишь такой пабликей в подписях, в стронгнеймах?
Конечно, конечно. Там, короче, было много интересного в те времена. Ну да ладно, да. В общем, в современном мире нет PDF-ки. Я не генерил последние лет 6-7. Ну, в общем, это довольно нормальная практика, когда вам нужно сгенерить какой-то PDF-документ, потому что PDF-ки, они много чем удобны. Ну и в частности, они неудобны, потому что очень много разных организаций их требуют.
потому что все привыкли обмениваться в этом формате, потому что в обычных документах, не знаю, как-нибудь Word или что-то у нас еще есть, Excel, какие-нибудь HTML-странички, они слишком такие. непрактичный. Ну, то есть у них там шрифты могут по дороге потеряться, у них может верстка поехать, у них может еще что-нибудь. А PDF это, наверное, такой самый практичный формат.
который вам более-менее гарантирует, что вы получите на печати. А мы любим печатать все подряд. И более-менее там таскает с собой все, что ему нужно. Там картинки, шрифты. всякую векторную графику умеет поддерживать. В общем, формат довольно полезный и, если так разобраться, для некоторых юзкейсов довольно уникальный. Поэтому существует очень много библиотек, которые помогают там стряпать PDF. Но на самом деле, в принципе... Самым простым способом получить красивенький PDF это через HTML.
потому что HTML обладает довольно богатым способом рендеринга. Вы там можете всякие таблички делать, картинки вставлять, отступы выравнивать, даже разноцветности какие-нибудь применять. И при этом... стандартный браузер умеет просто HTML-страничку сохранить в PDF. Поэтому самым большим лайфхаком таким бывает, если у вас не какие-то большие отчеты, не выпираетесь сильно в перформансы, у вас хватает простой...
В простой верстке обычной HTML-странички, то есть самый простой лайфхак, это возьмите HTML и просто сохраните в виде PDF. И обычно этого хватает в 80% случаев. Именно такими случаями и пользовался Rick Struggle. автор как раз статьи, которые хочется обсудить. Он для себя выработал, что, в принципе, ему всегда хватало такого подхода, поэтому у него была библиотечка, то есть не его библиотечка, он использовал специальную тулзу, которая называется WKHTML2PDF.
и ему его, в принципе, всегда хватало. Прелесть этой библиотеки была в том, что она была open-source, это была обычная такая команд-лайнерская утилита, которая под капотом запускала QtWebKit. И этот Cube WebKit просто рендерил страничку HTML в PDF, и все были счастливы. И это продолжалось много-много лет, пока...
Эта библиотека перестала поддерживаться. То есть она остановилась на какой-то старой версии Chromium. Она больше не поддерживалась. Там накопились какие-то уже старческие баги. Ну, в общем, морально устарела. Пора... Пришла пора переходить на какие-то новые инструменты, но все новые инструменты тем или иным способом оказались...
плохие. Ну, то есть, да, действительно, они тащили с собой новый хром, они там предлагали какую-то новую функциональность, которая нужна была, но базовые вещи, такие как, например, составление... Table of content. Как на русском переводится table of content? Это какое-то содержание. Что-то ты меня подвесил. Table of contents. Оглавление. Оглавление, да, ну вот типа автоматическое составление оглавления, допустим, по всем нашим заголовочкам ни у кого нет. Правильно расставлять...
Переносы именно страниц тоже мало у кого встречается. А если заинжектить какой-нибудь CSS для того, чтобы как-нибудь подправить, чтобы страничка уже готовая выглядела получше, вообще... мелко у кого есть, и если есть, то за деньги. Казалось бы, для такой вроде бытовой задачи использовать какой-нибудь close source или платить деньги слишком как-то овертельно было.
И поэтому Ник придумал, что в принципе у него есть WebView 2. А WebView 2 это компонент для Windows встроенный. Чем он прекрасен? На самом деле он прекрасен тем, что... Это стандартный Chromium под капотом, и этот Chromium, в принципе, вам ничего не стоит. То есть он у вас уже стоит, он не требует никаких денег, он бесплатный, и он может сделать вам банальное сохранение HTML-странички в PDF.
И этого в большинстве случаев хватало. То есть, по крайней мере, то, что делали многие продукты за деньги, Это можно было сделать с помощью данного компонента совсем без денег. Поэтому он решил, естественно, написать свою библиотечку, которая лучше всех остальных и объединит всех остальных. И давайте посмотрим, что же у него получилось. Во-первых, остановимся поподробнее на WebView 2. WebView 2 — это специальный контрол, который у нас встроен в систему Windows.
И он, по сути, является в Windows оберткой над Chromium. Если кто не знал, то Chromium с последних версий 10-11 винды поставляется как системный компонент. Для... компонента, который называется Microsoft Edge WebView 2 Runtime. То есть, на самом деле, под капотом там Chromium. И поэтому мы можем его использовать, если мы понимаем, что мы запускаемся на винде, мы можем рассчитывать, что он там уже есть.
То есть мы можем его полноценно использовать, при этом не таская с собой большой жирный хромиум, настоящий. При этом этот компонент инсталируется автоматически в последних версиях, если его нет. И самое прекрасное, он обновляется автоматически с помощью системы Windows Update.
То есть у вас всегда будет свеженький, красивый Chromium. Поэтому WebView 2 для Windows среды является довольно-таки заманчивым таким кусочком. Если копнуть поглубже, то это полноценный компонент, который используется в Microsoft Edge. Именно поэтому он всегда поддерживается в актуальном новом красивом состоянии. Так как это полноценный Chromium с Microsoft Edge, у него есть нативная поддержка, принтинг, диалога. И вы можете там настроить, каким образом вы хотите.
печатать HTML и частной функцией печати является экспорт в PDF. То есть там тоже настраиваются какие-то стандартные вещи. Отступы к цветам. хедра, футеры, вот это вот все. Вот эти все настройки там уже есть просто из коробки. То есть вам достаточно просто взять этот компонент, проставить вот эти настройки и вызвать одну функцию, и оно все заработает. Вроде казалось...
Половину тулзов, за которые мы раньше хотели платить, мы уже заменили. Но можно копнуть немножко и дальше. У WebView есть прекрасный API. который позволяет вам также внедряться хорошо в print, в PDF-экспорт. И благодаря этому API вы можете избежать какой-то показки диалогового окна для печати, для выбора, для еще чего-то. Вы можете в вашем приложении настроить неинтерактивное.
режим для того чтобы html конвертить в pdf без участия пользователя без ничего что в большинстве случаев как раз таки и хочется и по всем замерам это происходит довольно быстро то есть сам компонент довольно хороший Очень часто возникает ситуация, когда вы не сами стряпаете какой-то
HTML-документ, который хотите сконвертить. А вам уже нужно какой-то готовый, например, веб-сайт или страничку сконвертировать. Или пользователь вам подсовывает свою страничку, которую вы должны сконвертировать. И очень часто эти странички писались именно для веба. на HTML. То есть не были рассчитаны на то, чтобы их печатать.
У них, например, могут быть какие-то шрифты определенные, которые на принтере или на PDF-е выглядят плохо или не подгружаются. И у них могут быть неправильные разбиения по страницам. Ну, вообще, если мы начнем разбираться, то HTML, он... В HTML отсутствует понятие страниц. Если вы вдруг не замечали, то в интернете у вас вся веб-страничка.
рассчитано как один сплошной контент, то есть у него нет первой, второй, третьей, десятой страницы. А когда вы печатаете в PDF, по сути, экспортируете в PDF, там используется уже более такая типографическая система, там уже есть страницы. стандартного соотношения А4 обычно и так далее. То есть разбение на страниц становится довольно важным.
И, например, почему это важно? Ну, потому что, когда вы пользуетесь стандартным гугловским экспортером в PDF, он вообще не загоняется о разбиениях. Он может вам разбить посередине, допустим, какой-то параграф. или перечисление из списка разбить посередине, или, например, заголовок вашего абзаца оставить на одной странице, а весь контент перенести на другую страницу. То есть то, что нормальный верстальщик, нормальный человек никогда бы так не сделал. В общем, у Хрома это...
Поведение по умолчанию. И очень полезно бывает это подсунуть свой CSS стиль. Свой CSS стиль, в котором вы можете объявить такие атрибуты, как PageBreakInside. И break inside. То есть вы можете выключить возможность брейка внутри, например, параграфов или после заголовков. Что тоже бывает очень-очень полезно. И тогда переносы начинают работать правильно.
В общем, зная всю эту магию, Ник написал свою библиотечку, которую вы, в принципе, можете легко использовать. Это пакет HTML2PDF Library, он устанавливается через Nougat. и позволяет вам довольно легко, быстро и просто сконвертить HTML в PDF с довольно интересными и хорошими настройками, которые вы из коробки сразу не достанете.
У него есть ограничения. Как я уже сказал, он использует WebView 2. Это значит, он работает только под Windows. Но у него есть плюсы. Благодаря этому ограничению он не тащит с собой весь вот этот хромиум, что обычно таскают другие инструменты. То есть это... Использует нативный компонент, который есть у линды. Ну, естественно, он open-source, это пакет, он бесплатный к использованию и все такое. Из интересной кастомизации он может настраивать очень гибко отступы, ориентацию.
какие страницы печатать и так далее. Из неочевидных моментов, но, наверное, каждый из вас это ожидает, это то, что он поддерживает хорошую синхронность во всех своих методах, может писать как файлы, в стримы и много еще куда. и поддерживает неинтерактивный режим. То есть вы можете, например, генерировать PDF внутри ISP Netcore приложения. И это на самом деле не так уж и просто, как кажется на первый взгляд, потому что сам компонент, вот этот WebView 2, он очень сильно прибит.
к десктоп-приложениям. То есть он поставляется как Windows UI-компонент, а это значит, что ему нужен Windows Event Loop. Как известно, в ASP.NET никакого Event Loop нет. Пол статьи как раз посвящено тому, а как Ник пытался побороть эту проблему. Даже не пытался, а как он ее поборол.
То есть у него там были очень суровые хаки и по запуску потоков с правильными флажочками, и по перехвату cancellation токенов. В общем, если кому-то интересно, посмотрите. Но факт остается фактом. Он добился того, что можно это использовать в ISP Netcore приложении. И это одна, наверное, из киллер-фичей этой библиотеки. А дополнительные фичи, которые у него есть, он добился того, чтобы можно было генерить
содержимое документа, если вам это сильно нужно, который автоматически собирается по всем заголовкам документа. И, в принципе, если вы откроете такой PDF, у вас там будут такие букмарки, закладочки, по которым вы хорошо и легко сможете навигироваться. Как я уже говорил, стандартный Chromium из коробки такого делать не умеет.
Также он добился того, что можно легко и красиво сделать CSS Injection, то есть вы можете поднастроить тот документ перед рендерингом, который вам дали, как раз внести туда какие-то дополнительные вещи, какие-то дополнительные атрибуты, которых там раньше не было. Например... Ставить правильные разбиения в правильное разбиение страниц. Давайте же посмотрим на альтернативы. Если вдруг перед вами стала такая задача, какие альтернативы у вас есть?
Ну, во-первых, вы можете использовать специальный DevTools Automation Tools. Это, например, инструменты, которые с помощью DevTools API могут управлять Chromium. Эти инструменты используются, например, в частности, очень сильно при тестировании. Например, Microsoft Playwright использует такую штуку и Pupiter Sharp. библиотечка, что они делают? Они запускают Chrome, к этому Chrome подключаются через DevTools API.
в качестве клиента, то есть Chrome запускается в качестве сервера, они подключаются в качестве клиента и начинают его автоматизировать, то есть гонять Chrome и делать с ним всякое. Также, если вам не нужна какая-то большая кастомная настройка, вам просто хочется без лишних каких-то усложнений сконвертить существующую страничку в PDF, то это можно сделать с командной...
с командной строки любого хромиума приложения. Chrome, Edge, Vivaldi, еще что-нибудь. У него есть специальный параметр, с помощью которого вы можете запустить его и сканерить все, что хотите. Но если вам нужна уже какая-то кастомизация, если вам нужны вот эти вот... дополнительные фишечки разбиения в css injection и прочее то вот смело можно порекомендовать пакет ника поэтому если вдруг перед нами такая задача стояла
то рассмотрите, данная библиотечка, по-моему, довольно интересное, элегантное и хорошее решение для многих случаев. Да уж, я смотрю в современном мире прям непросто, непросто изгенерить PDF. Хорошо, что мне этого не надо делать. Ну, да, да, в принципе, задача в общем случае довольно сложная. Слушай, ну, когда ты начал говорить про Windows Event Loop, и вот это все. Вот, да, тяжело.
Ну ладно, то есть это все только на винде? Да, это все на винде, если хочется какой-то кроссплатформенности, гибкости и все такое, то там только платные компоненты, там уже надо идти в деньги. Понятно, ну ладно, пойдем пока тогда в другую сторону. а в другую сторону мы пойдем все-таки в SPNet Core и в WebApp и в аутентификацию. Не помню, когда мы последний раз касались темы аутентификации, но мне кажется, про всякие authentication handler мы говорили как-то очень давно, а тут попалось тоже...
Небольшая статейка про то, что делать, если вам нужна не модная молодежная аутентификация по GVT, вот этому всему, а просто по обычному API-Key. То есть у вас есть API-Key. И в зависимости от того, валидный он или невалидный, нужно разрешить или запретить действие. Автор статьи говорит, что есть несколько способов это сделать, и один из способов, который он упоминает первым, и говорит, что не надо им пользоваться, это реализация фильтров. То есть вы можете реализовать I-authorization фильтр,
и в нем что-то проверять, что-то отклонять и так далее. Но это как бы неправильная часть, потому что все-таки это авторизация. Автор считает, что по APKU возможно вы определяете в том числе, кто такой этот пользователь. И значит, что это скорее в том числе аутентификация, поэтому лучше использовать для этого специализированное средство, а именно Authentication Handler. Для этого вы можете сделать свой собственный хендлер.
Для этого вы создаете новый класс, наследуетесь от AuthenticationHandler, это стандартный ASP.NET корный класс, и дальше вы, соответственно, таким образом встраиваетесь в пайплайн основного ASP.NET. Понятно, что там есть всякие разные тоже практики для API-ключей, если вы ими вдруг решите пользоваться, что они там должны правильно генерироваться, криптобезопасно.
Вот это все, только что TPS, их нужно ротировать, при логировании нужно быть очень аккуратным, чтобы эти API-ключения посвятились в каких-нибудь урлах, в сообщениях об ошибках или в логах. Но, тем не менее, если вы вдруг решили ими пользоваться, и вы решили реализовать свой authentication handler, для начала вы в этом authentication handler валидируете ключи. Вероятно, для этого у вас будет какой-то свой собственный интерфейс, apk-валидатор.
который, собственно, возвращает валидный ключ или не валидный, и, возможно, возвращает, например, что это за пользователь, проверив по какой-нибудь внутренней базе данных. А после этого вы... в этом месте, в этом хендере создаете объект принципала. Как я люблю эту терминологию. Принципалы с клеймами. Как перевести клеймы на русский?
Утверждение. Декорация утверждения, наверное, да, самая близкая. Ну вот, по-моему, в книжке по SPN Core мы переводили как утверждение, но я тут где-то встретил, а по-моему, чуть ли не в этой статье, нет, где-то в интернете я встретил перевод как претензии. И там был типа... принципал с претензиями в тексте статьи. Претензия, что твой юзер ID равен такому-то, как-то не звучит. Утверждение, что моего юзера ID-шник такой-то, это в принципе нормально. Но клеем это же действительно претензия.
Действительно, когда ты посылаешь какую-нибудь претензию, по-английски это будет claim. Это тоже верно. Поэтому, видимо, был какой-то автоперевод, и в итоге получился такой. Вот этот самый токен с претензиями. Ну да ладно. Короче, создаете принципал, создаете клейм, его возвращаете, и все нормально. Дальше, помимо вот этого самого хендлера, помимо основного метода, нужно еще переопределить метод handleChallengeAsync.
который как раз-таки нужен для того, когда он будет вызываться в тот момент, когда у вас по какой-то причине нет вообще, например, APK или еще что-то, именно в нем вы отвечаете 401. с нужными заголовками, которые подскажут клиенту вообще, с какой авторизацией к вам нужно приходить. Естественно, нужно использовать problem details, если что, вот это все, но дальше вот вы имеете authentication handler, и что с ним делать? Его нужно как-то зарегистрировать.
Оно делается все через метод addAuthentication, и там есть следующие варианты. Если вы просто сделаете addAuthentication, то понятно, что будет использоваться стандартный, обычный. аутентификация, стандартный пайплайн, а спинадкора, то есть по-моему, по дефолту, по-моему, только куки добавляются, мне кажется. Но сейчас не скажу. Что-то я давно стандартного уже не пользовал.
Но вы можете указать вашу схему. Как раз-таки туда вы передаете APK в функцию addScheme. Вы передаете APK в ваш authentication handler в качестве типа. параметра и передаете, соответственно, дефолтная схема, это ваша схема, и все будет работать по дефолту. Ну, дальше понятно, что еще в Ашке этот самый валидатор нужно закинуть тоже в DI. И все бы хорошо, но такой способ будет хорошо и прекрасно работать, только если у вас только API-кейл идентификация.
Если же у вас вдруг, например, вы хотите поддержать в вашем сервисе аутентификацию и по API-ключу, и по GVT, и по, допустим, cookie, то не будет достаточно закинуть
три схемы аутентификации и с этим работать. Ну, вернее, как, на самом деле будет, и работать оно будет, но оно будет работать очень своеобразно. Вы будете получать эроры в логах, потому что Esperant Core по дефолту он начинает перебирать схему идентификации, типа одну, вторую, третью, пока не найдет либо ту, которая скажет «Окей», либо пока не переберет все, и они не скажут, типа «Все, тогда нельзя».
Вот, а зачем нам Aurora в логах? И для того, чтобы это все нормально работало, там нужно тогда уже регистрировать свою политику по выбору своей authentication, то есть там регистрируются дополнительные классы, которые... могут изучить ваш реквест и по, допустим, наличию нужных заголовков выбрать нужную схему аудентификации и так далее. Автор сказал, что это все очень сложно и поэтому это будет в следующей статье.
Как только она будет, мы обязательно про нее расскажем. Но на самом деле мне очень понравился комментарий к этой статье. И это интересный вопрос, который я тоже себе в какой-то момент задавал. А вот я тут рассказал, что нам нужно переопределить стандартный класс, написать его, значит, куда-то там закинуть в DI. Хитрым способом добавить эту тифонную схему авторизации. Вот это все внутри этого.
класса authentication handler создавать там прицепала с клеймами, вот этим всем. Вместо всего этого, написано в комментариях, почему не сделать просто? В самом мидловарке получились заголовка Просто написать свою медловарьку, которую получили из заголовка apk, проверили его, ну, тем же, допустим, валидатором, окей. Если окей, то пошли дальше, если не окей, ну, значит, как бы ответили 401. Нафига вот это все устраивать и так далее.
Я для себя в какой-то момент ответил, что... Если вы гарантированно знаете, что у вас будет всего один вариант, то, наверное, можно и так, но поскольку заранее требования не знаешь, то проще встроиться в стандартный и получать стандартные принципы, стандартные клеймы, стандартные все. И работать дальше независимо от того, как там прошла аутентификация. Но, не знаю, возможно, есть другие мнения. Слушай, ну у тебя же вот этот хендлер, он же появился...
получился, по сути, мидлварькой, только более такой строго типизированный. Да, да, да. И тут скорее вопрос, сама-то мидлварька, она простая, там понятно, что ты взял запрос, и там, я не помню, что он возвращает, он, по-моему, какой-то там результат возвращает, если я правильно помню, или исключение.
для этого. Что-то такое там было, по-моему. Ну, как просто. Тебе все равно нужно будет сходить в базу, проверить подпись, сделать принципалы. Все то же самое. Ну, вот вопрос сделать принципалы, как бы, может быть, можно было бы без принципов. Мне кажется... А как ты current user будешь использовать? Не знаю, прокинул асинклокалом. А? Ну, так то же самое. Асинклокалом прокинешь как раз принципала.
Ну вот, а синклокалом я прокинул свой объект, и тут какой-то принципал с какими-то клеймами. Да нет, я понимаю, о чем ты, и вот это то же самое в каком-то смысле, о чем размышляю я, что проще встроиться в стандартные, и тогда любой стандартный компонент... знает где достать стандартного пользователя в цепочке SPNET.
И со стандартными клейвами и всем добром. Несмотря на то, что вся эта монструозная конструкция с принципалом, всеми клейвами, скоупами, что ему можно задать, она смотрится, возможно, оверкиллом для каких-то простых приложений. И действительно, у вас там будет всегда один клейм, и все, и больше ничего. Ну, значит, так и будет. Но зато стандартно. Ну, да-да, там же целая обвязка, целая инфраструктура вокруг, которая на это рассчитывает, на это работает. Вы получаете кучу там...
всех дополнительных бенефитов. Ну и эта боль становится очевидным, когда система усложняется, когда у вас действительно не один способ входа, а несколько способов входа. А еще лучше, когда у вас есть не одно приложение, а несколько приложений, которые должны атактироваться через единый сервер. Например, мы недавно прикручивали apk для Identity Server.
Тоже очень увлекательное занятие. Вообще не устаю поражаться, насколько гибок и прекрасен Identity Server вообще. То есть мы опеки прикрутили довольно легко, просто там и свободно. Вот, и там у вас уже несколько вариантов. То есть, допустим, приложение... приложение приходит куда-то к стороннему, к другому ресурсу,
И оно хочет тоже обращаться к этому ресурсу по API-ключам. У вас есть вариант сделать как здесь, допустим, на каждый запрос, идти к Identity серверу, отдавать ему API-ключ, просить можно или не можно, получать принципала и доступаться к ресурсу. Но делать это на каждый запрос это мовитон. Для этого появились у нас GVT-ключи. Поэтому мы легко сделали так, что сначала ты, как клиент, можешь через опеки обменять себе его на какой-то временный access token.
А потом по этому Access Token уже ходить непосредственно к ресурсному приложению. При этом ресурсное приложение это Access Token вполне способно провалидировать по цифровой подписи и работать с ним точно так же, как с обычным GVT. В общем, гибкость там обалденная. В общем, и встраиваемость в Identity Server тоже прекрасная. И там у нас, естественно, куча там всяких схем, куча челленджей, подключены какие-то сторонние системы.
И на вот такой большой сложности ты понимаешь, что все вот эти принципалы, клеймы, мапперы, хендеры, аутентификаторы, они имеют смысл. Потому что каждый из них выполняет свою работу, и когда они все вместе связываются, получается довольно такая хорошая, мощная интерпрайс-система. Ну, а если вы делаете, конечно, свое какое-то приложение, которое раздает котиков, то, наверное, в Middleware проверить какой-нибудь базик.
аутентификацию, и этого для вас достаточно будет. Ну, может быть и так, да. Но будьте готовы, если что, перейти на более полноценное решение. Почему нет? Ну что, давай дальше. Продолжаем про практически прикладные штуки. Практически преклонная штука. Говорим практически про тестирование, да? Как ты часто любишь, Игорь, тестировать? Да, ну, юнитест пишу, да. Вот, и насколько часто тебе подводились инструменты для ассертинга? Никогда. Ну, в смысле, нет, я путал expected и actual. Было.
Наверное, все путали. Пока не заставил себя запомнить все-таки, в каком порядке. Или ты начал какую-то стороннюю либо использовать? Нет, я, по-моему, ни в одном проекте никогда не использовал сторонних либо. Ну, то есть, у меня очень много тестов было на InUnity, мне кажется, что в InUnity
assertions в какой-то или вообще все время вынесены в отдельную любу, unit assertions, или namespace там только отдельный, что тут я не помню, честно говоря, но каких-то прям совсем сторонних я не использовал, нет, только то, что идет в комплекте с фреймворком. Ну, я думаю, что все вы уже догадались, что мы пытаемся рассказать про нашумевшую новость, которой, в принципе, у нас не было, но про нее каждый обязан знать. Это то, что Fluent Assession сменили лицензию. И сменили ее...
довольно интересно, странно, безумно. Давайте поговорим об этом. Начиная с версии 8, Fluent Assessions распространяется под двойной лицензией. то это для них значит. Они выбрали себе коммерческий путь с восьмой ветки. Бесплатно вы можете использовать теперь только не... в некоммерческих продуктах. А вот если у вас коммерческий продукт, то там вы должны отвалить практически 130 долларов за каждого разработчика в год. Это довольно такие...
Большая сумма. Данное предложение действует в связке с компанией Exit. Я так чисто по документам не очень понял. Они то ли продались компании Exit. То ли Exit выкупила право на ту часть, которая коммерческая, то ли они с ними взаимодействуют. В общем, там, как всегда, хитрожопные какие-то английские высказывания, из которых ничего не понятно. Ну, в общем, казалось бы, компания Exit, которая продает уже очень долго...
Большие качественные компоненты для Enterprise всяких WinForm, DPF-форм и прочих форм. В общем, контролы для графических интерфейсов довольно мощные. Она должна уметь в цену как-то играть. У нее должны быть какие-то эксперты, которые понимают, сколько можно выставлять цену, а сколько нельзя выставлять цену. В общем, она 130 баксов за библиотеку, которая сравнивает объектики, циферки, константы.
Это как-то слишком много. Ну, она интерпрайзно сравнивает там с безопасностью. Как-нибудь с поддержкой интерпрайзной. Вообще-то...
Да, наверное, но как бы то, что было, обычно людям хватало, да, то есть вряд ли кому-то нужна была энтерпрайзная, была, да, никто не догадывался, что им нужна была энтерпрайзная поддержка для Asertian Library, который как бы сравнивает до числа. Теперь, наверное... Ко многим такая мысль начала постепенно приходить, что если вам нужно сравнить результат, который вы ожидаете, результат, который у вас имеется, для этого нужно заплатить 130 баксов.
Вот, интересно, опять же, да, у всех не вызывает в основном возмущение то, что она стала платной, но, в принципе, много сейчас пытается библиотеку пенсоров стать платными, это мелочи. У всех возникает, возмущает очень сильно эта цена.
Мне понравился ролик Ника Чапсуса, когда он там тоже у него подгорал, он рассказывал, что, а думайте, если люди как бы это не IDE, это не Photoshop, это не какой-то редактор видео или 3D Studio Max, там нет, это простая библиотечка, которая сравнивает пару объектов.
В общем, из-за этого вы должны платить как бы 130 баксов. В общем, довольно какое-то странное решение. Пока оно кажется мегабезумным для любого человека, который умеет думать, но почему-то все с серьезным рылом обсуждают, что да, да, что такое коммерциализация, это хорошо. И прочие вещи. Ну, опять же, что нам делать? Игорь, как нам теперь осёрд это делать-то? Да руками. Ну, слушай, если нужны какие-то экстенджены, пишем сами. Чё, нет?
За 130 долларов на человека в год можно, мне кажется, и написать. Можно нанять Джуна и пусть он как бы пишет. Ну, как бы я не знаю. Видишь, я не пользовался флуантами. Для меня синтаксис выглядит... Ну, скажем так, мне не нравится, короче, такой синтаксис. Совсем он очень многословный, и вот это все. Но...
Опять же, я не думаю, что там какой-то миллион разных extension методов. То есть там, наверное, самое сложное, это только вот именно сама логика сравнения объектов, по сути. Там с обходом полей, рекурсивно вот это все. Да, это иерархия, это сравнение иерархии объектов. Какие-то поля можно выкинуть, какие-то сравнить, для которых основный компайр настроить. Иерархическое наследование, да. Это киллер-фича, который мало у кого есть, можно даже сказать, ни у кого такой нет.
Но тут вопрос, ну подожди, тут, наверное, вопрос у нас наверняка есть в библиотеке, которая позволяет именно просто сравнить, не в формате, что это осёршено, а просто сравнить. Ну вот, библиотека сравнения. порыться, может есть, может нет, я не знаю. Вот, и поверх этого построить extension методы именно с assert'ами, ну, мне кажется, несложно.
Наверное, несложно, но, опять же, есть, наверное, отдельные библиотеки, просто стандартных assert library такого нет, да, то есть это, можно сказать, вот конкурентное преимущество было. Вот, насколько там часто вы этим пользовались, это уже вам решать, это уже вам смотреть.
Я, кстати, тоже не особый любитель того, чтобы сравнить два числа, для этого писать как бы флюенс синтаксис на нативном английском языке через точки, поэтому я тоже предпочитаю стандартный iSword Library, и мне, в принципе, ее хватало. Но надо признать, что библиотека довольно распространенная. Там по скачиваниям Снугета она там входила в какие-то топы. Поэтому многим людям, видать,
Что же им делать? Ну, во-первых, нужно запретить обновление пакета старше, чем ветка 7, а на 8 версию не переходить. Я надеюсь, у вас у всех есть локальные пакетж-менеджеры, с помощью которых вы контролируете свои зависимости и кэшируете локально все, чтобы не лазить в интернет на каждый билд. И вот там практически у всех этих пакетж-менеджеров есть настройка, что...
Какие версии пускать, какие не пускать. Можете смело блокировать все, что больше восьмой ветки, потому что седьмая официально остается всегда свободной. Также точно можно посоветовать прекрасную библиотечку, которая умеет мигрировать Fluent Assertion под стандартные XUnit Assertion. Если вам интересно, то будут ссылки в шоу-нотах, посмотрите. Библиотека на Рослине, которая просто одним движением руки мигрирует все, что есть. Какие-то сложные вещи, наверное, она не сможет перенести.
В качестве примера это какое-нибудь иерархическое сравнение. Но если вы пользовались только какими-то стандартными вещами, то она очень много всего умеет и постоянно развивается, там автор хорошо дописывает, поэтому, возможно, вам ее хватит.
Если вдруг вы любитель все-таки вот этого Fluent Syntax, если вы не хотите возвращаться на стандартные асерты, то сейчас самым популярным решением, которое все рекомендуют, является библиотека Shootly. Она... практически с тем же самыми словами, с теми же самыми точками и наворотами, как и Fluent Asession, помогает вам делать практически все то же самое.
У нее сейчас тоже не хватает иерархического сравнения такого мощного, как во Fluent Asession, но я думаю, что авторы на волне хайпа в ближайшее время все это допилят. Вот, в принципе, такие альтернативы, я думаю... Не слишком большая потеря, как бы все найдут, куда перейти. Поэтому пользуйтесь, смотрите и наблюдайте почаще за новостями в ваших любимых open-source библиотеках. Потому что они могут стать вдруг нечаянно не только не open-source, но и очень-очень дорогими.
супер, минус еще одна библиотечка, плюс, возможно, многие новые, а может и нет, действительно, непонятно, ладно, поглядим, как пойдет, пойдем в новости, на самом деле, неожиданная. Не то, что новость-то сама не неожиданная, она вполне ожиданная. На самом деле довольно старая. Это новость про nullable reference type в F-sharp. И вот это как раз странно. И мне что-то мы обычно не берем.
какие-то большие темы про F-Sharp, потому что не то, чтобы мы в нем большие специалисты, но тут мне как-то стало интересно разобраться, как это все устроено. Я статью прочитал, наверное, даже в ней почти все понял. Поэтому давайте красненько пробежимся, потому что я на самом деле считал, что с налом был референт с тайпами. В F-Sharp проблем нет, потому что в F-Sharp, ну, как бы нет проблем с налом.
Но оказалось, все не так просто. Потому что в F-Sharp нет налов, поэтому проблем с ними нет. В F-Sharp действительно нет налов, но проблема в том, что если ты пишешь только на F-Sharp, тогда нет налов. Но стоит тебе заиспользовать хоть что-то написанное, например, на C-Sharp, а у нас языки же все-таки на одной платформе, поэтому бывают иногда такие случаи, то в этом случае сразу становятся возможны налы, потому что в C-Sharp это у тебя могут быть налы.
Более того, дефолтный тип string, например, в C-Sharp, он же может содержать null, поэтому если тебе какой-нибудь метод из C-Sharp-овой библиотечки возвращает string, то в F-Sharp-е добро пожаловать в потенциальный null-reference. Вот, и с этим надо что-то делать. Соответственно, исторически Fsharp действительно старался все это минимизировать и все это проверять в compile-тайме как можно больше. Особенно потому, что все стандартные, грубо говоря,
наиболее популярные типы, это рекорды, discriminated union, которых мы все еще ждем в C-sharp, таплы, функции, анонимные рекорды, они все не позволяют туда запихнуть null никак. Вот. Можно повесить атрибут allow null literal, и тогда можно в такие штуки запихнуть null, но, во-первых, этот атрибут можно повесить только на кастомный тип, и он все еще не доступен на рекордах, юнионах.
и там еще на некоторых вариантах. Если же вам нужно что-то, где вы бы использовали, например, Now в C Sharpie, то в F Sharpie вместо этого лучше использовать какой-нибудь там тип Option. в котором, соответственно, есть сам, да, когда есть какой-то результат и нан, и дальше по аторматчингам с этим что-то делать. Но... F-шарповый мир был бы хорош, если бы не было C-шарпа в каком-то смысле, потому что есть интероп, и если у вас есть reference type, который приходит из C-шарпа,
то F-Sharp-компилятору не остается ничего другого, кроме как считать, что на нем висит атрибут allow-null-literal, потому что, ну, кто его знает, что там от этого C-Sharp-а придет, враждебного, так сказать, мира. Но бонус и хорошая новость состоит в том, что со времен как бы... того, как зарелизили первые версии C-Sharp с такой парадигмой, F-Sharp, прошу прощения, C-Sharp тоже не стоял на месте, и потихонечку, потихонечку кодовая база C-Sharp размечена таки атрибутами nullable reference типов.
Они там довольно заговористые бывают, там всякие null, if not null, и вот это все. Но, по крайней мере, есть разметка, которая позволяет понять, а может ли вот из этого все шарп-кода прилететь хоть какой-то null. И теперь F-sharp научился этому тоже немножко следовать. И можно теперь в F-sharp-овом проекте включить специальную настройку Nullable Enable, как у нас, собственно, это делается в C-sharp.
И после этого у вас, с одной стороны, компилятор будет пытаться понимать, что же там делает этот самый C-шарповый код, в том смысле, какие атрибуты на нем развешаны. а с другой стороны появился новый синтаксис. То есть в F-Sharp появился синтаксис или null. То есть вы можете теперь по сути сделать union с null. Или там, грубо говоря, вы пишете вертикальную черту, как вы обычно это делаете, для создания обычных юнионов. Но вместо второго типа вы пишете слово null, ключевое.
И да, соответственно, в этом случае у вас referenceTip будет явно уметь содержать null, без необходимости повесить атрибуты low null literal. Вот. Таким образом, это более языковая фича становится, нежели чем такая. Не знаю, компиляторно-рантаймное, что ли? Компиляторное, скорее. А не рассказывается, почему они по умолчанию не включили? Ну, вроде кажется, логичная штука, что через C-sharp может прийти любой референс-тайп в виде NALA. Она должна по умолчанию быть включена.
Ну, нет, как раз наоборот, если включить по умолчанию NullableEnable, то у тебя получится, что тип, ну, то есть ты вроде как объявляешь, ну, у тебя там стринг, да, допустим, из функции возвращается. Но string, если ты включишь nullable enable, то string будет означать ненулябельный string.
Обязательно. Да, но теперь стандартная библиотека вся размечена, и тебе там вернулся бы нулябельный стринг, а не просто стринг. И если тебе вернулся нулябельный стринг, то тогда ты его обязан объявить в C-Sharp теперь. Ты должен его объявить как стринг. вертикальная черта null, и тогда включить nullable enable и пожалуйста. То есть, грубо говоря, это аналог того, что ты бы в C-Sharp написал стринг вопросик.
Да, просто я про то, что сейчас в C-Sharp, если ты создаешь новый проект по дефолту, то у тебя Nullable Enabled включены. Поэтому String и Nullable String — это, в принципе, два разных понятных типа. По идее, по дефолту в F-Sharp такая же интерпретация. C-sharp-ного кода должна быть включена. То есть он должен понимать string, nullable, string, что это два разных нулябельных типа. А то, что по дефолту это поведение выключено. Ну, вроде как, да. По крайней мере, пока я так понял, что ровно так.
Вот. Может они просто пробуют, а потом... Ну, вероятно, да, вероятно, не знаю, но по дефолту вроде как выключено. Значит... Дальше, соответственно, если, ну и широкомпилятор, понятно, научился там правильно генерить нужные ворнинги для всяких potential dereference nulls,
То есть если он понимает, что тут как раз нулябельный вариант, то есть типа string вопросик в терминке C-Sharp, а вы к нему просто деревья не сетите, и там будет null потенциально, то он, конечно, вас предупредит и скажет, что тут надо вообще-то попроверить. что там и кто там. А еще там есть всякие кастомные штуки, типа, например, он будет проверять, что toString всегда возвращает ненулябельную строчку, то есть toString не имеет права возвращать null.
в шарпе, и, соответственно, я, кстати, не знаю все шарпи, можно из ту стринга анал вернуть? Ну, если он раньше всегда обычный стринг возвращал, который, по сути, может быть, ну, да, скорее всего, да, всегда. Да-да-да, вот в шарпе, короче, нельзя, и это будет в уровне компилятора. если вы включили null ability. Дальше. Ну, соответственно, pattern matching улучшить. Можно нормально написать null в тех местах, где нужно. Вот. И...
самое интересное про Type Inference. То есть Fsharp вообще... известна, ну, одна из, не то что одна из ключевых фичей, но довольно важная фича в том, что очень часто не нужно писать типа, потому что компилятор практически очень умный и выводит, ну, все, что можно сам. Но снова был reference type. Проблема вот в чем. Допустим, у тебя на вход функции пришла какая-нибудь функция. Какой-нибудь тип, который нулябельный. Если ты все шарпи напишешь if там...
С какая-нибудь строка пришла. Не равно null, да, и дальше что-то внутри ифа. Внутри ифа компилятор умный и знает, что внутри ифа у нас строка точно не null, да, и к ней можно безопасно dereferencing ее, и вот это все. Не будет никаких вордингов, ничего. F-шарповый компилятор, несмотря на... крутой вывод типов и всего такого, не умеет пока, по крайней мере, во Flow Analysis, и поэтому всякие if, when, else и while, если вы напишите внутри с if-ами вот эти все,
то там оно никак не влияет на nullability. И даже если вам пришла нулябельная строка, вы напишите if, проверите на null, внутри она все равно будет считаться нулябельной. И на dereference будет все равно warning. Удобно, неудобно. Ну, пока неудобно, да. Но, видимо, ресурсов не было допилить компилятор до такого уровня. Пока для того, чтобы это обойти, рекомендуется все-таки делать это нормально через pattern matching. Там все вроде неплохо. Ну, в случае с pattern matching.
И, может быть, это чуть более такой функционально правильный подход, чем писать императивные ифы. В паттерматчинге там нулябельность нормально выводится. В будущем, соответственно, ну да, вот они явно пишут, что мы посмотрим на юзер фидбэк, мы посмотрим, как это вообще все взлетит, не взлетит, и будем думать. То, над чем они сейчас уже точно думают, это над аналогом наших...
Операторов вопросик точка, то есть это вызов методов, если у вас ссылка на объект может быть null. И двойной вопросик, как он там, не помню, как он называется. который позволяет вернуть левую часть выражения, если она не нал, и правую часть выражения, если левая часть оказалась нал. Как они будут выглядеть, и какое количество стрелочек, минусиков и прочих спецсимволов они будут...
выражены в F-sharp, я пока не знаю. Они пока думают. Возможно, есть где-то про позу, где можно это все посмотреть. Я так сходу не смотрел. Ну да, это же чисто императивная такая штука, вызвать, если не null, то у них же там должна быть какая-нибудь монанда, лямбда, чтобы вызвать метод, которого null.
Ну и опять же идут разговоры, будут смотреть на фидбэк, на тему того, как это все работает со всякими проверками типов в рантайме, с анбоксингом, вот этим всем, потому что это все рантаймовые штуки, компилятору... Ну, в общем, очень сложно в этом месте понять, что там будет с nullability. Потому что какой реальный объект туда придет, может быть понятно только в самом рантайме. Так что тут будем думать. В общем, на удивление оказалось, резюмируя всю статью, что...
Несмотря на то, что я так считал Fsharp таким null safe языком все дела, оказалось, там есть о чем, есть что посмотреть. Там все не очень тривиально, несмотря на очень умный компилятор, он не всемогущ и много где не может пока захендлить nullability. Так что последим-последим, посмотрим, во что это выльется. А еще такой момент, что, как я понял статью, есть пока некоторые... С одной стороны, есть штука, что F-sharp...
будет стараться на своих сборках размечать метаданными свои типы с учетом вот этих атрибутов nullable reference type. То есть, грубо говоря, если вы наружу выставляете не nullable какой-нибудь там union, то F-sharp повесит на эту сборку метаданные для этого там ретурного значения, что оно non-null. Так что C-sharp-овый компилятор, соответственно, будет знать, что вот если эту сборку позвать, там будет правильная метаданная и точно не null. Вот это с одной стороны.
а с другой стороны в обратную сторону, соответственно, как я понял, там не все атрибуты пока поддержаны, то есть всякие вот эти вот хитро вывернутые null, if not null, вот это все может быть так вот... Там тоже не до конца еще все поддержано, поэтому тут тоже будут развиваться. В общем, последим, посмотрим. Мне стало прям интересно, чем это закончится. Надо поглядеть в мир Fsharp тоже немножко.
хотя бы одним глазком. А, кстати, да, если я вдруг в чем-то сильно наврал или все переврал и, значит, все на самом деле совсем не так, пишите нам в комментах, приходите, расскажите, как на самом деле. Ну, да, действительно, язык интересный. К сожалению, мало у нас экспертов по нему, поэтому заходите, особенно если вы захотите прям что-то вообще экспертное донести. То будет красота. Можно прям докладик даже организовать. Ты сразу на докладик. Давайте хотя бы на 5-10 минут в подкастик прийти.
Я люблю фундаментальный подход. Ну давай, книгу сразу писать. После доклада засядем. А в аудиоформате очень легко пишется. Точно. Ты пробовал читать код на F-sharp? Голосом. Голосом? Да я, слушай, на C-шарпе-то он голосом не очень выглядит, а если так визуально, то... А на C-шарпе вертикальная палочка, скобочка, угловая.
Вот это все? Ну, короче, такое. Наверное, да. Специфично, специфично. Ты просто будешь называть не вертикальная скобочка, а там типа применение монады третьего уровня к пятому эквивалентному преобразованию. Хорошо, ладно. Давай, давай какую-нибудь тему попроще. Давай попроще, если как раз поговорить, это топ-10 ошибок, найденных в C-Sharp проектах. Нормальная же тема. За 2004 год. 24, да? То есть как раз. 25 начался. Интересная тема как раз про то, чтобы завершить...
прошлый год и посмотреть, а что ж у него такого интересного было найдено. Найдено по версии PVS-студия. PVS-студия — это анализатор. Не только C-Sharp, но много всего. Там, прежде всего, плюсовые, C-Sharp-ные коды, Java-код. В общем, они много чего умеют анализировать, но нам интересно. Естественно, C-Sharp-ные проекты, они очень много пишут на Хабара про свой анализатор и так далее. В принципе, меня всегда, когда я...
я смотрю вот эти отчеты PV Studio, у меня такое ощущение, что нормально люди так не пишут, это какие-то просто идиоты. Но когда мы видим настоящие большие проекты, которые мы сегодня в том числе рассмотрим, и видим вот эти идиотские ошибки, которые там есть...
Просто дивы даешься, как это все работает. А некоторые даже не идиотские ошибки. Некоторые ошибки, в принципе, вполне нормальные. Я тут парочку нашел прям таких ого-го, о которых даже как-то не задумывался. Поэтому я тебе предлагаю, давай посмотрим. чем люди дышат, какие до сих пор продолжают делать ошибки уже в 2024 году и что из этого получается. Автор расставил ошибки по 10 местам.
Я бы их расставил по-другому, но в принципе почему бы и нет, какая разница, тут главная формальность. Поэтому пойдем точно так же по порядочку, посмотрим, с 10 начнем место. На 10 месте находится Unity 6 и его интересная ошибка об Equalsy. Вот это, кстати, мне кажется, довольно неочевидная штука, и вот она могла бы там в каких-то первых местах вполне сиять. Давайте поподробнее. Сейчас у нас будет много кода словами, поэтому приготовьтесь, насторожитесь, на острите ушки. И погнали.
Когда вы переопределяете класс или структуру, когда вы хотите переопределить метод equals, обычно у вас есть два метода equals. Один строк оттипизированный и другой, который принимает объект. который принимает объект, он объявлен на данном обжекте. И обычно что он делает? Он проверяет, что если этот объект равен нашему типу, то вызывает уже строго типизированный equals, который принимает...
уже строго типизированный наш объект, и начинает у него какие-то поля проверять. Так вот, довольно интересная ошибка может с вами случиться. Когда вы в метод equals от объекта Пытайтесь понять, а ваш это объект или не ваш это объект. Обычно как это делается? Это делается переменная, которая к нам пришла other, is, например, в данном случае это был searchIndex, и тогда вызвать equals непосредственно с этой переменной.
Но проблема заключается в том, что если вдруг вы этот метод скопипастили, и на самом деле он находится не в классе searchIndex, на который проверяет оператор is, а в классе searchField, и строго типизированный метод как раз переопределен для searchField. И вы-то думаете, что equals вы вызываете со строго типизированным search филдом. Но на самом деле вы вызываете equals с search индексом. А это значит, что вызовется метод equals от объекта, в котором вы как раз таки сейчас находитесь.
Если у вас мозги не свернулись трубочку и вы проследили весь мой workflow, то вы понимаете, что это рекурсия. Это рекурсия, которая легко приводит к Stack Overflow, если вдруг вам реально в ваш equals придет Search Index, то есть тот тип, с которым вы по идее должны были просто вернуть false. Все это упадет как бы.
StackOverflowException. Довольно занятная штука, опять же, если без какого-то инструментария, который подскажет вам, что здесь рекурсия, мог глазками это не всегда очевидно можно найти. Тем более, кто там всматривается в эти equals и на code review? Наверное, никто. Хорошая ошибочка. Девятое место тоже должно в первых, наверное, быть. И список проектов, в которых была найдена эта бага, оно подтверждает мои слова. Только вдумайтесь. Эта бага была найдена в проектах Garnet, RavenDB,
MSBuild, Roslin и, внимание, сама PVS Studio. В общем, хорошая такая бага. И она как раз нами говорит, что многие разработчики не знают, что это вообще ошибка. Поэтому внимательно. Возможно, в ваших проектах она тоже есть. Ошибка заключается с тем, что ForEach не умеет работать с налами. Как она выражается? Если вы возьмете некий контекст,
Из этого контекста запросите какой-то там элемент, и у этого элемента попросите список реквестов. И все это хотите прокручить в forEach. Но есть одна тонкость. Когда вы запрашиваете контекст, реквеста у этого контекста может не быть. То есть элемента, к которому вы хотите обратиться, может не быть. И в таком случае вы можете поставить вопрос . И уже у этого вопрос . запрашиваете список элементов. То есть вы пытаетесь прокрутиться.
по условному оператору, который в случае, если вдруг реквеста не существует, вернет вам null. А если существует, то вернет полноценную коллекцию реквестов. Когда он вернет полноценную коллекцию реквестов, вопросов нет. Но вот если реквеста не существует... и полноценную коллекцию он вам вернуть не сможет, то вернется из-за оператора вопросик, вам вернется null. И на самом деле фурич с null работать не умеет. Он возьмет вам и выбросит исключение.
И исключение это будет выбрасываться в методе getEnumerator, потому что enumerator табрать не у кого больше. В общем, вот таким образом, якобы защищаясь от null reference exception, мы здесь получаем null reference exception, но только немножко в другом месте, в getEnumerator. В общем, кажется, что разработчики не понимают, что фурычить по коллекции, которая может быть null, не стоит. Это большая-большая ошибка. Именно поэтому у нас столько много проектов, которые были ей подвержены.
Так, что у нас еще есть? Восьмое место заслужил Нобкоммерс. Это такой интернет-магазин. Ну, он сделал довольно банальную штуку. Он и сделал query в базу данных, там join всяких наставил, фильтров, выбрал объект, а потом это все сохранил в переменную и сделал этой переменной точкой distinct. И все.
Точка distinct – это метод, который не модифицирует самую переменную. Он возвращает результат уже модифицированный. То есть, по-хорошему, как это исправить? Это нужно было положить к query равно queryDistinct. То есть, вот этот запрос сохранить. в переменную. Это тоже, наверное, частая ситуация, но мне кажется, большинство анализаторов уже научились с ней справляться, и разработчики уже в этих местах более такие внимательные.
Седьмое место это Unity. Извечная проблема с стринг-форматом. Мне кажется, со стринг-форматом больше всего каких-то проблем возникает. Но вот и здесь. В стринг-формате... пользователь, программист, пытался вывести 4 элемента, сделал даже темплейт, в котором указал 4 placeholder и даже передал 4 переменных.
Но проблема в том, что у этих четырех холдеров, он указал всего лишь навсего индексы 0 и 1. И у третьего тоже 0, и у четвертого тоже 1. То есть, несмотря на то, что передаются 4 перемены и 4 плейсходера, покажутся реально только два первых аргумента. 0 и 1. Такая тоже довольно банальная ошибка со стринг-форматом, вечная, наверное, мы их никогда не заведем. На шестом месте подвергся анализу сервер Diablo 3.
Слушай, кстати, я вот поймал себя на мысли, знаешь, когда мы с тобой все время пытаемся вспомнить, какие большие интересные проекты написаны там на Волоне, написаны на C-шарпе в конце-то концов, где вот все эти разработчики, которые миллионами пишут на C-шарп проекты. Мне кажется, из отчета PV Studio я вот...
узнал больше интересных, открытых, опенсорс, там, больших проектов, которые написаны на C-Sharp, чем из рекламы Microsoft. Ну, это да, хотя, с другой стороны, слушай, ну вот для меня, ну, Unity понятно, но Commerce, ну, тоже понятно. оно звучало. Garand Raven DBMS built, окей. Но вот сервер Diablo 3, наверное, пожалуй, самый удивительный из этого списка. Честно скажу. Ну да, хороший...
Хорош, такой большой полноценный сервачок. Нормально поддерживается, нормально пишется на C-Sharp. В принципе, быстрый, красивый. Нормальный проект. Погнали ближе к нашей теме. Итак, что же там случилось? Ну, там случилась тоже такая банальная вещь. который очень часто я в плюсах еще видел, это когда мы вызываем какой-то метод и передаем эту переменную, в данном случае передаем переменную count, и справа ставим два плюсика.
То есть в надежде на то, что мы передадим переменную, инкрементированную на плюс 1. Но так как два плюсика у нас поставлены справа, то у нас сначала произойдет передача значения в метод, а после того, когда мы из метода вернемся, эта переменная увеличится на 1. Это явно не то, что хотел по коду разработчик. Ну, непонятно. Слушай, иногда это как раз то, что хочется передать, а потом увеличить на единичку. Не зря же изобрели постфиксный оператор.
Да, там по коду было видно, что это он все-таки ошибся. А, ну хорошо. Надо было сначала заинкрементить, а потом передать. Это можно было бы сделать, написав эти два плюсика слева. То есть сначала заинкрементить, а потом передать. И так как вообще это была локальная переменная. можно было просто написать allcount плюс один и вот его передать. С ним какого смысла там выпендриваться и вот эти постфиксные операторы делать смысла не было. Так, под пятое место у нас под раздачу попал сам .NET 8.
Вот не хухры-мухры. Давайте рассмотрим немножко код. Что сделал программист? Программист проверил, что если stack count меньше определенной длины, он возвращает... возвращает null, то есть выходит из метода. А потом у этого стека делает метод pop. Но здесь есть небольшие тонкости. Stack в нашем случае это нулябельная переменная. То есть он проверяет ifStack
вопросик наш, вопросик точка каунт меньше длины, тогда выходим. Иначе стек, и здесь подавление компилятора восклицательный знак, поп. То есть разработчик рассчитывал на то, что строка стек вопросик count при стеке равным null вернет ему true при сравнении с длиной. Но на самом деле это не так. Естественно, у нас тернарная логика и вернет...
Это строка false. То есть, по сути, нулябельный стэк вполне пройдет это условие, и нулябельный стэк провалится в следующую операцию. А следующая операция это pop, притом с подавлением компилятора. То есть, тут чистой воды будет null reference exception. Отсюда можно сделать два вывода, что в Microsoft тоже работает непонятно кто, но самый главный вывод это в том, что никогда нельзя пользоваться оператором подавления анализа нулябельных.
То есть у меня в многих проектах прям запрещено использовать оператор восклицательный знак. И прекрасно живется на современных фреймворках вообще без проблем. Поэтому если у вас есть такая возможность, обязательно ставьте восклицательный знак оператор в качестве ROR. Анализатор ReSharper позволяет такую штуку делать. И никогда не используйте. Прямо на руках сразу. Через поиски бейте по рукам. На ревью. Вот.
Это самый главный вывод, наверное, который можно сделать из этой статьи. В общем, никогда нельзя подавлять компилятор. Компилятор ваш друг. Если вы не умеете писать код, который удовлетворяет компилятор, значит вы плохой программист. Четвертое место. Опять .NET 8 попал под раздачу. Да что ж такое? Ну, здесь ошибка тоже довольно банальная. Мы крутим в цикле все инстансы. Притом цикл через for по индексу. И когда мы...
проходимся по всем этим инстансам и пытаемся обратиться к текущему инстансу. И вместо того, чтобы написать instance от i, то есть от текущего счетчика, мы обращаемся по статическому индексу. instance от нуля. То есть, всегда возвращается нулевой элемент данной коллекции. Ну, бага такая. Понятная, примитивная, в принципе, довольно старая. Могла бы быть когда угодно, не только в 2200 году. И, скорее всего, и была.
Третье место. Опять Unity 6 попал под раздачу. Вот тут уже веселее, потому что нам встречается вик-референс. Наверное, не все наши слушатели знают, что такое вик-референс, поэтому я немножко объясню. Это специальный такой тип. Используется он довольно редко. Вообще, чаще, наверное, я его встречал, когда мы писали под WinForm еще. Вот в WinForm, когда нужно подписаться на какие-то события, при этом не словить утечку ресурсов, вот там таблицы на викреференсах, это было прям...
что must-have обязаны были все знать и спрашивали даже на собеседованиях. Как только мы переехали под какой-то там бэкэнд, вик-референсов я, наверное, не встречал просто никогда. Ну, опять же, если вы кто-то про разработчик, то вы, скорее всего, про них знаете. Вик-референс — это такой интересный объект, который...
сохраняет ссылку на ваш объект. То есть можно представить, что это какой-то тип переменной, который сохраняет ссылку на объект. Но в отличие от других переменных, то есть филдов или пропертей, Garbage Collector имеет полное право у вас этот объект собрать. Если вы присвоите значение...
переменной какое-то значение, то это значение собираться не будет. Если вы приспроизводите это значение переменной vcreference, то оно может горбочек коллектором собраться. Это как раз нужно для того, чтобы не удерживать
переменную, если вдруг нам не хватает памяти, то есть вполне можно освободить эту переменную, но вдруг garbage collector еще не собирался, а там какая-то большая переменная, которая нам создавать второй раз лень, мы можем ее вполне заиспользовать. В общем, довольно хороший use case, и он часто пригождался. Но в коде, соответственно, с такими викреференсами нужно правильно работать. Это нужно знать и нужно уметь. Что же написали у нас в Unity 6? В Unity 6 написали...
обращение как раз работы с этим вик-референсом. Во-первых, все взяли в лог, и в этом логе сказали, что вот у нас есть переменная вик-референса. Давайте мы у нее проверим флаг, который называется isAlive. Это действительно флаг, который говорит, что собрал грибш коллектор эту переменную или не собрал. А дальше обращаемся к этому reference target, то есть тому объекту, который...
мы храним и который Garbage Collector может собрать, кастим его к необходимому нам типу и вызываем у него прямо через точку метод, который мы хотим у этого типа вызвать. Ошибка заключается в том, что с тех пор, когда Ив проверил Isalive, И до того момента, когда мы вызвали у этой переменный метод, garbage collector пардл вполне легально мог ее собрать. Поэтому мы в этот момент рухнули с null reference exception. Здесь...
Как вообще нужно работать с викреференсом и в частности в этом случае? Нужно было сохранить эту переменную в... Не переменную, а вот этот таргет. Нужно было сохранить таргет из викреференса в обычную переменную. И как только вы сохраняете ее в обычную переменную, уже Garbage Collector...
этот объект не имеет права собрать, так как на него уже есть нормальная ссылочка, которая уже внутри вашего скопа. И тогда уже можно его как-то проверить и что-то с ним делать, как-то использовать. Здесь еще также есть лог. И, возможно, автор надеялся, что лог ее каким-то образом спасет. То есть, может, это какая-то критическая секция, в которой ГЦ не работает. Но на самом деле, ну, в лог здесь абсолютно ничего не...
не делает, естественно, лог никак на Garbage Collector не влияет, поэтому сборщик вполне может это все собрать, и точно так же вы получите null reference exception. Довольно интересная штука, несмотря на то, что викреференсы пользуются мало, но... Ошибка нетривиальная и интересная из-за этого. Второе место. Ридос. Это тоже довольно, в принципе, уже оригинальная вещь, про которую нужно знать, и многие разработчики про это не знают. Что ж такое Redos? Нашли, кстати, эту ошибку в Screen to GIF.
утилити. Опять же, новая утилита для нас. Смотри, которая написана на C-Sharp, которая довольно хорошо пользуется. То есть она записывает экран в GIF-ку. чтобы там удобно кому-то это отправить в какую-нибудь службу поддержки, допустим, или друзьям показать, что красиво на десктопе у нас происходит. Итак, в чем же, соответственно, заключается смысл Redos-атаки? Эта атака способна замедлить или даже вообще повесить ваше приложение на основании уязвимого регулярного выражения.
Если в это регулярное выражение передать какую-то произвольную строку, есть возможность передать какую-то произвольную строку и у злоумышленника есть возможность сформировать эту строку где-то там далеко и передать ваше приложение, то в принципе у него есть большой шанс. ваше приложение завесить. Это актуально только для RegExpo, которые вычисляются на основе недетерминированного конечного автомата.
В общем, такие тоже есть. И это актуально только если в вашем RegEx используется определенная конструкция. Ну, например, в нашем случае это произвольный объект с плюсом и группировка с плюсом. получается такая рекурсивный автомат. Если у вас такая штука используется, то вам необходимо все-таки немножко...
подстраховаться. Есть специальные флаги для RegExpo, есть специальные флаги для Escape и так далее. В общем, там есть способы, как это решить. В данном случае в утилиту ScreenToGif этим RegExpo проверялся название входного файла. И поэтому просто можно было в интерфейсе ввести специальную сформированную строчку, она довольно несложная, и завесить приложение с помощью вот этого RegExpo, то есть полностью вывести его из строя.
Для десктоп-предложений, конечно, это не так критично, но если вдруг у вас такой регекс используется на сервере, то вообще легко можно вывести ваш сервер в отказ в обслуживании.
Поэтому тоже повнимательнее, если вы используете сложные какие-то регулярные выражения с рекурсии, обязательно должны про эту штуку знать. И тут интересно, что это не какая-то банальная... паттерн-матчинг по коду или анализатор токенов, это нормальный такой флоу, security-флоу, где PVStudio проверила, откуда к нам пришел этот параметр.
какое там регулярное выражение, есть ли в нем рекурсия. И на самом деле, в принципе, PV Studio умеет проверять в том числе дефекты безопасности. Она может искать SQL-инъекции, LDAP-инъекции, XSS, XIE и многие другие. То есть такой, в принципе, не то что полноценный flow-анализ security, но показывает, что с многими частыми распространёнными вещами справляется. Ну и на первое место попал .NET 8.
Не слишком экстравагантная ошибка, но, в принципе, тоже интересная. Ошибка заключается в том, что разработчик использовал интерполяцию строк вместе с форматом строки. То есть он написал у текст-райтера, вызвал метод write, который принимает первым аргументом формат, и остальными аргументами он принимает непосредственно значение, которое он должен подставить в этот формат. И в данном случае разработчик написал...
Формат. Там поставил правильный placeholder в фигурных скобочках нолик. Но при этом он задал... знак доллара в начале строки. А это значит, что компилятор будет эту строку считать строкой интерполяции. И если в этой строке интерполяции в фигурных скобочках задан нолик,
то он не пойдет вам подставлять какой-то первый параметр. Он просто этот нолик заинтерпретирует как C-шарпный код и в частности просто как обычное число 0. Поэтому в качестве конечного результата мы получим просто-напросто строку, в которой вместо плейсхорда поставлено число 0. вместо того, чтобы подставить какие-то аргументы. Не то чтобы критично в данном случае, но в некоторых ситуациях это может быть довольно опасно и довольно страшно.
Такой интересный топ. Да, мне интересно, почему это стало топ-1. То есть я бы как раз второй пунктик поместил на первое место. Она такая более и технически сложная, и с точки зрения опасности. Такая простая штука с интерполяцией. Не знаю. Ну да, я согласен с тобой, что нумерование здесь как-то выбрано странно. Может, потому что это .NET 8. Базовый фреймворк, который везде есть. Либо .NET 8, либо, может быть, еще...
может быть, там как-то учитывалась частотность, то есть, грубо говоря, если, ну, конкретно это вот пример типа в .NET 8, а фактически их таких было дофигища разных всяких, не знаю, может быть, в этом еще фишечка. Ну, может быть. Автор говорит, что он, в принципе, учитывал все. И популярность в библиотеке, и чисточность, и критичность. И все это с коэффициентом на его усмотрение. Поэтому не будем сильно придираться к...
именно к местам. Если вы хотите подробнее этот код все-таки слушать не ушами, а посмотреть глазками, посмотреть... в каких проектах он есть, и подробные статьи прочитать, там практически есть подробная статья про каждую уязвимость, тем более вот про сложные уязвимости, там есть прямо отдельные статьи, чтобы как раз себя просветить и в своем коде такого не делать, в общем, поэтому милости просим по ссылке, все мы предоставим в шоу-ноторах.
Да, все сделаем. И что у нас на сегодня? Кратко о разном осталось только? Да, давай кратко пробежимся о разном. И уже, наверное, хватит. Ну что ж, начнем тогда, наверное, из интересного у нас будет... А вот закончился второй сезон книжного клуба. Да, кто не знал, у нас на dot.net.ru портале есть еще подкаст, который называется «Книжный клуб».
Ведем его не мы, но ведут его замечательные парни. Это Роман Гошков, Григорий Кузьмин и Роман Щербаков. И они разбирают книги. Сейчас закончился второй сезон. Они разбирали книги Алекса Сюй.
Алекс Сюй. «Систем дизайн. Подготовка к сложному интервью». Очень популярная книга, интересные кейсы. И, в общем, ребята проходились довольно-таки плотно по всему материалу, который там есть. Если для вас... тема актуальна, то, опять же, в виде такого аудиокниги, которую мы сегодня уже упоминали, вы можете послушать, как реально практические инженеры, которые там сталкиваются с систем-дизайн, но не только в собеседованиях, но и в реальной жизни, в общем, что они об этом, обо всем думают.
Кстати, первый выпуск был 26 ноября 2023 года, ну, то есть больше, чем год ребята обсуждали эту книгу, то есть основательно хорошо подходили, все разбирали. И, как я уже упомянул, это... был второй сезон, а в первом сезоне была книга .NET Microservices Architecture and Containerized .NET Applications. Несмотря на название, никакого уклона на Containerized там нет. В общем, не только там контейнерные приложения рассматриваются в этой книге. Эта книга вообще про микросервисы, про...
то, как нужно настроить архитектуру приложений, про то, как взаимодействовать с внешним миром, ну, естественно, в том числе про контейнеры. Поэтому даже если вы не сильно используете контейнеры в вашем приложении, первый сезон тоже про .NET, про .NET-архитектуру, .NET-приложения. Тоже интересно послушать. Книга популярная, интересная. И, опять же, ребята проходились по ней довольно плотненько и основательно. Такие два сезона книжного клуба у нас закончились. В общем, те, кто ждал...
Окончание сезона для того, чтобы послушать все подряд, а не ждать через недельку. Вот. Те дождались. Можете начинать. Так, еще одна у нас в чате интересная тулза сплыла. Называется она Велопак. Велопак это инсталлятор. который может сделать оффлайн-инсталлятор вашим приложением. Опять же, речь на про десктоп, скорее всего, приложение. Никакому ISP-нет, бэкэнд, фронтэнд, вот это все не надо. Но у нас много десктоп-приложений, почему нет? И им часто нужен способ распространения.
Это какая-то больная тема. Было очень много инсталляторов, было очень много чего-то предложено. Но... Как-то они то ли устаревают, то ли не поддерживаются, то ли бросают их. Ну, в общем, с первого виду хорош инсталляторы, но умирают. И вот, кстати, появился очередной велопак. Он позволяет вам не только заинсталлировать ваше приложение, но и также хорошо и легко его обновлять. Инсталлятор кроссплатформенный, естественно. Практически не требует никакой конфигурации. Все довольно...
просто настраивается и просто делается, там, с полпинка заводится. Он умеет инсталлировать, обновлять, притом обновляет он именно дельтами, может, а не целыми пакетами закачивать, что важно, если у вашего клиента, кастомера, не такая стабильная связь. И все это делает он самостоятельно, без всяких каких-то настроек.
Распространяется на Windows, OS X и Linux, поддерживается. Никак не зависит от языка. Вы можете туда засунуть абсолютно любой язык, не только C-Sharp проекты. Безусловно, инсталлятор справится с чем угодно. Легко переиспользовать. и умеет обновлять ваши пакетжи, ваши артефакты прямо с GitHub, что тоже удобно. Вы там настраиваете на GitHub pipeline, в pipeline у вас выдаются какие-то бинарники, и эти бинарники непосредственно подхватываются инсталлятором, и ваши клиенты счастливы.
что у них всегда хорошая последняя версия. Тоже прекрасно. Новость от Microsoft. WinForms продолжает нас радовать, развиваться и прыгать семимильными шагами, ибо для WinForms вышел WinForms Roslin Analyzer. который теперь умеет какие-то специфичные вещи для WinForms делать. Например, в 9.NET у WinForms появился асинхронный API, и вот... Аналайзер прекрасно с этим асинхронным API уже работает. Он там может рассказать.
как там cancellation токены необходимо пробрасывать в этот асинхронный API и так далее. То есть если вы переводите свой, например, синхронный API на асинхронный, аналайзер вам прекрасно зафиксит все места, куда надо пробросить cancellation токены. Ну и прочие тоже там интересные вещи чисто для...
для дизайнеров, для сериализации правильной, для WinForm. В общем, там тоже у него поддерживается. Это только начало, типа первый анонс какой-то. Автор обещает, что будут его поддерживать. Автор, сам Microsoft, автор, между прочим. Сам Microsoft обещает, что будет его поддерживать, развивать. писать новые анализы. Поэтому WinForm живее всех живых, умирать не собирается, обзаводится нарослыми анализаторами. Ну, в добрый путь. Почему бы и нет? Больше хороших анализаторов, хороших разных.
Да, все прекрасно. Ну, что, на этом будем заканчивать. Поговорили сегодня про пулы объектов, поговорили про то, как конвертить HTML в PDF. Немножко затронули тему аутентификации в SPNet Core. Обсудили измену лицензии Fluent Assertion, поговорили про наоборот референс тайпов в F-Sharp 9, обсудили список ошибок, найденных ПВСом в проектах за 2024 год, ну и кратико пробежались по таким небольшим новостям. На этом, наверное, все на сегодня.
Да, да, точно все. А также подписывайтесь, рассказывайте о нас своим друзьям. Если вдруг там поставите нам где-нибудь пятерочки, звездочки, яблочки, что вы там ставите, мы будем очень признательны, потому что это увеличивает наши рейтинги в поиске, нас чаще находят, чаще слушают.
И это хорошо. Будем просвещаться все вместе. Если у вас есть какие-то интересные статьи или интересные какие-то блоги, за которыми вы следите, читаете и хотите, чтобы они появились в наших выпусках, то смело присылайте нам на почту, почту, указанную во всех шоу-нотах. Ну, кажется, все. Всем шашари, лайки, репосты. До новых встреч. Пока. Всем пока.