Обновить
128K+

.NET *

Хаб со знаниями про .NET

125,79
Рейтинг
Сначала показывать
Порог рейтинга
redb.Core
redb.Core

«Это написал Клод» — комментарий из-за рубежа, и почему идее хранилища больше двадцати лет (даже если так было бы, то клод выпивая тонные кофе это всё родил ...)

Под англоязычной версией статьи про SQLite-провайдер на dev.to появился комментарий в духе «даже без "load-bearing" и тире через всё предложение видно, что это писал Клод». Не первый раз слышу что-то подобное, так что решил ответить не в комментариях, а отдельным постом — заодно расскажу то, что давно собирался: откуда вообще взялась идея хранилища, на котором всё это стоит.

Сначала честно про долю ИИ

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

Показательный пример — сам релиз 3.6.0: баги, которые в него вошли, нашёл не Клод, а реальная эксплуатация. Модель не может споткнуться о краевой случай, который проявляется только под живой нагрузкой с непредсказуемым вводом — такое не ловится по аналогии с соседним кодом.

А теперь — откуда это всё вообще взялось

Основная идея хранилища — не изобретение последних месяцев. Первая версия появилась в 2004 году, когда я писал на Delphi — за двадцать с лишним лет до того, как Клод вообще начал существовать.

Задача была — дать объекту динамические поля на ходу, не фиксируя их жёстким классом заранее. Delphi для этого уже нёс нужный кусок — RTTI (Run-Time Type Information): каждый класс несёт метаданные о своих полях и свойствах, доступные в рантайме, не только на этапе компиляции. Поверх этого — интерфейс IDispatch из COM: GetIDsOfNames резолвит имя поля в DISPID, Invoke вызывает по этому DISPID, передавая значение через VARIANT. Вместе это давало то, чего не даёт обычный жёсткий класс: объект мог обзавестись полем, которого не существовало на момент компиляции, а вызывающая сторона — спросить о нём по имени и получить настоящий типизированный ответ.

Система прожила у меня внутри собственных проектов много лет, никуда не публикуясь. За это время она полностью пережила Delphi и COM — переехала на .NET, механизм сменился до неузнаваемости (никакого VARIANT, никакого DISPID — сейчас это типизированные колонки в Postgres/MSSQL/SQLite, которые я уже разбирал построчно в статье про 13 таблиц), а вопрос остался ровно тем же: как дать объекту гибкий набор полей, не потеряв возможность спросить "а какого оно вообще типа".

Отсюда, кстати, и название — RTTI-based storage, не маркетинговый термин, а прямое родство с тем самым механизмом Delphi. И не EAV — там никогда не было обезличенной колонки "значение", RTTI всегда знало настоящий тип.

Клод помогал полировать это перед тем, как это увидело свет публично. Сама идея и её первое воплощение — старше Клода на десятилетия.

Цикл про redb:

Всё лежит в публичных репозиториях — redbase-app. Предлагаю не гадать по стилю прозы, а склонировать к себе и прогнать нейронкой — глубоко, очень глубоко. И приглашаю на дискуссию: если найдёте что-то, что выглядит как архитектурное решение именно от модели, а не от человека, который держит всю систему в голове годами — с удовольствием обсужу предметно.

Теги:
+5
Комментарии0

Пассажир меняет билет прямо в дороге — а маршрут собран из трёх GDS и ж/д. Что происходит с данными

Сотруднику нужно долететь до одного города, доехать поездом до другого, и обратно тем же путём — одна командировка, билеты из разных систем бронирования. А потом он уже в дороге пишет в телеграм: "планы изменились, летим не туда, перебронируй".

Три системы под самолёты — Amadeus, Sabre, Travelport: исторически несовместимые XML-диалекты одного и того же понятия перелёта, выросшие из мейнфреймов 60-80-х, каждый со своими причудами и полями, которых нет у соседа. Плюс отдельная, никак не связанная с ними система бронирования под железную дорогу — свой формат, своя логика мест и классов, ничего общего по структуре с авиационными GDS. Запросить всё это параллельно, свести разноформатные ответы в одно и собрать из них валидный маршрут по стыковкам — уже само по себе задача не для россыпи if и ручных мапперов.

А потом человек уже в дороге меняет одно плечо маршрута. Один перелёт уже случился — трогать нельзя. Один — впереди, подлежит замене. И весь маршрут не по шаблону "туда-обратно", а любой длины и состава: сегодня две пересадки, завтра — четыре, послезавтра прямо в дороге вставили лишний вылет.

Тут ломаются две разные вещи, и почти всегда решают только одну.

Первая — как описать саму логику поверх этого зоопарка источников. И сбор из четырёх систем разом, и ветка "можно менять / нельзя менять" превращаются либо в DSL на языке общепринятых интеграционных паттернов (Scatter-Gather, Content-Based Router — тот же словарь, что у Apache Camel), который прочитает и поймёт человек, ни разу его не писавший, — либо в код, который через полгода не восстановит и автор.

Вторая — куда положить результат. Маршрут — не два поля outbound/return, а последовательность разнотипных плеч (самолёт ≠ поезд), где число элементов и состав не известны заранее и меняются посреди собственной жизни объекта. Стандартный ответ — либо гора nullable-колонок под все виды транспорта разом, либо миграция на каждый новый вид.

Разбираю, что происходит, когда обе оси — читаемый DSL для потока и пластичная типизация для формы — закрывает один связный инструментарий, а не сшиваются вручную посередине, каждая своими костылями.

Скоро.

ссылки: хабр redb.ru

Теги:
+3
Комментарии3

Как redb хранит сложные объекты: не бесхемная свалка, а RTTI

Про redb (RedBase) есть два зеркальных заблуждения. Первое: раз объект пишется «как документ», внутри лежит сериализованный JSON или блоб. Второе, противоположное: раз всё падает в общую таблицу значений — значит это плоский мешок пар «ключ-значение» без схемы.

Оба мимо. То, что внутри — это RTTI, полноценная система типов, живущая в самой базе. И устроена она заметно сложнее, чем и блоб, и «атрибут-значение». Разбор архитектуры я подробно давал в отдельной статье на Хабре: «redb: реляционное хранилище объектов» — ниже сжатая суть.

Слой типов: база знает настоящий тип каждого поля

redb хранит не только данные, но и их описание типов — три связанных уровня:

  • types — реальные дескрипторы типов (db_type и соответствие .NET-типу);

  • _schemes — сами типы (классы), с поддержкой наследования через self-reference;

  • structures — типизированные поля схемы: имя (name), тип (_id_type, FK на types), признак коллекции (collection_type) и вложенность (_id_parent).

Значения в values всегда привязаны к конкретной структуре (id_structure, FK на structures). Поэтому строка values — это не безымянная пара «атрибут-значение»: это значение известного, именованного, типизированного, возможно вложенного поля известного класса. База в рантайме знает, что перед ней — decimal Salary в схеме Employee, а не абстрактный «атрибут №42». Это и есть RTTI.

Хранение коллекций: построчно, реляционно

Вложенные массивы, словари и глубокие иерархии redb раскладывает в _values построчно, а не строкой:

  • Никаких JSON-блобов на диске. Каждый элемент коллекции — отдельная строка с типизированными колонками (_value_longvaluestringvaluedatetimevalueguid, …), внешними ключами и обычными индексами.

  • Связь и порядок — реляционные. Вложенность собирается self-reference колонкой arrayparent_id (FK на values.idON DELETE CASCADE). Порядок массива и ключи словаря — в arrayindex (text: '0','1','2' для массивов, строковый ключ — для словарей).

  • Один элемент — одна строка. List<OrderItem> внутри класса не превращается ни в JSON-поле, ни в десяток физических таблиц, которые вы заводите руками.

Что это даёт на практике

  1. Честный LINQ на уровне СУБД. Данные лежат в типизированных колонках, а метаданные структур позволяют движку собрать нативный SQL: Where / OrderBy / GroupBy / оконные функции идут по реальным индексам базы, а не перебором JSON в памяти бэкенда.

  2. Загрузка за один запрос без каскада JOIN-ов. Чтобы поднять объект со всей глубиной вложенности (пусть там 20–30 списков), не нужен каскад JOIN, как у EF с .Include(). Плоская структура забирается из _values одним запросом и собирается в объект в памяти.

  3. Точечный Change Tracking (Pro). При сохранении Pro-версия строит деревья ValueTreeNode (память против БД), сравнивает их (ValueTreeBuilder / ValueTreeDiff) и шлёт UPDATE только по изменившимся узлам — граф целиком не перезаписывается.

Итог: redb совмещает удобство работы с объектами «как с документами» и фундамент реляционной СУБД — типизацию, индексы, FK и запросы, которые исполняет база, а не бэкенд. Ключ к этому — не блоб и не плоский мешок атрибутов, а persisted-RTTI: typesschemesstructuresvalues.

Ссылки

Теги:
+4
Комментарии0

redb ecosystem
вышла версия 3.5.0(1) nuget
в ней Локальная база на клиенте
также новый коннектор redb.Route.As2 github
добавлены EIP паттерны
Message History EIP 
XSLT transformation
Routing Slip EIP 

посмотреть можно здесь github.com/redbase-app

хабр

Теги:
+5
Комментарии0

Прямо сейчас я заметил, что моя статья получила дизлайк, и карма упала на два пункта.

На Хабре немало публикаций в духе: «Я попал в Яндекс, смотрите какой я крутой».
В них - ноль пользы и никакой информации.
Другие материалы поражают скомканностью и поверхностностью.

Я заметил любопытную закономерность: чем больше пустых слов и ерунды выливает автор, тем больше лайков он получает. Для примера: свою статью об опыте работы я написал наспех, без особого старания - и она спокойно прошла по показателям. А когда действительно стараешься, вкладываешь силы, результатом становится минус карма.

Особенно неприятно, когда у человека аккаунт с накрученной кармой: он сидит, поливает всех грязью, сам при этом ничего полезного для сообщества не сделав.

И вот я думаю: стоит ли вообще делиться знаниями на этой площадке, если здесь хватает людей, которые портят атмосферу? Может, есть достойные альтернативы Хабру?

Особенно обидно, когда корпеешь над статьёй месяц или два: собираешь материалы, изучаешь источники, структурируешь информацию, углубляешься в тему - и всё это ради минуса в карме. Ожидаешь чего угодно, но только не негатива, а получаешь именно его.

Я целый день правил статью, перечитывал материалы, улучшал подачу - и в итоге получил такую обратную связь. При этом моя статья (и не одна) пробилась в топ Google и Яндекс-поиска, где информации по этой теме почти нет в таком разжёванном виде.

Я пока новичок здесь, хотя у меня есть друзья, которые уже обжигались на Хабре и в итоге просто плюнули на него, перестав публиковать материалы. Поэтому особенно интересно услышать опыт других участников.

Хочу осветить эту проблему хотя бы пока есть карма.
Уже видно, как работает эта система.

Ты, читатель, знаешь, сколько труда стоит такой материал?
Сколько сил уходит на его подготовку?
И если ты думаешь, что это сгенерировано ИИ - то ошибаешься.
Настоящая работа всегда отличается от «сырых» статей без структуры.

Если считаешь себя крутым рецензентом - напиши свою статью.
Покажи, что можешь лучше, а я посмеюсь, как ты ни строчки сделать не сможешь.

Теги:
+9
Комментарии36

Бенчмаркая CSE: подстава на uint — деление считается дважды

Уважаемые читатели, в этом посте я хочу разобраться, что компилятор делает с парой / и %, и представить свои выводы.

Возьмём число 47 и делитель 10. Деление даёт 4, остаток — 7. Компилятор деление не выполняет: он умножает 47 на подобранное число и сдвигает, получая 4. Дальше для остатка хватает вычитания: 47 − 4 × 10 = 7. Одно умножение на оба ответа.

Так на int. На uint компилятор получает 4, умножает на 10, вычитает — а потом заново считает те же 4 из 47, вторым умножением.

Ответ верный и там, и там. CSE, common subexpression elimination, находит повторяющиеся вычисления и считает их один раз. Оба умножения в паре считают одно и то же, и на int проход их склеивает. На uint не склеивает — отсюда и обращение в трекер.

int value = ints[i];
total += value / 10 + value % 10;    // одно умножение

uint value = uints[i];
total += value / 10 + value % 10;    // три умножения

Замер на 1 024 значениях, .NET 10, три машины. Значения положительные: у знакового деления отрицательные идут другой веткой.

.NET 10. Пара к делению — во сколько раз пара медленнее одного деления того же типа
.NET 10. Пара к делению — во сколько раз пара медленнее одного деления того же типа

Из таблицы можно сделать выводы:

  • на int пара занимает столько же времени, сколько одно деление, на uint — вдвое больше;

  • беззнаковое деление быстрее знакового в 1,54–1,60 раза: знаковому нужна коррекция для отрицательных значений;

  • проседает не тип, а пара операций.

Вот как это выглядит в машинном коде, Xeon W-2255. У int одно умножение на всю пару:

mov      edx, 0xD1FFAB1E      ; подобранное число
imul     edx:eax, r9d         ; единственное умножение, вышло 4
sar      edx, 2               ; деление готово
lea      edx, [rax+4*rax]     ; 4 x 5
add      edx, edx             ; ещё x2, вышло 40
sub      r9d, edx             ; 47 - 40 = 7, остаток

У uint то же самое, но в конце деление идёт второй раз:

mov      r10d, 0xD1FFAB1E     ; то же число
imul     r10, r9              ; первое умножение, вышло 4
shr      r10, 35              ; деление готово
imul     r10d, r10d, 10       ; второе: 4 x 10 = 40
sub      r9d, r10d            ; 47 - 40 = 7, остаток
mov      r10d, 0xD1FFAB1E     ; снова оно
imul     r8, r10              ; третье: те же 4 заново
shr      r8, 35               ; и тот же сдвиг

Были проверены ещё три случая, разницы между int и uint в них нет. Делитель 16, степень двойки: деление сводится к сдвигу, остаток берётся из младших битов, умножений ноль у обоих. Делитель в переменной: работает машинная команда деления, она выдаёт оба ответа разом, 2 714 против 2 716 нс. Тип ulong: на .NET 8 и .NET 9 было три умножения, на .NET 10 осталось одно, а у uint три.

Что делать на практике:

  • в горячих циклах вроде разбора числа по цифрам, форматирования и хэшей пара идёт на каждом витке, а с ней и лишнее умножение;

  • Math.DivRem возвращает к одному умножению: быстрее пары в 1,13–1,76 раза. Внутри для uint то же вычитание:

// dotnet/runtime, Math.cs

public static (uint Quotient, uint Remainder) DivRem(uint left, uint right)
{
    uint quotient = left / right;
    return (quotient, left - (quotient * right));
}
  • вычитание, записанное явно, value - value / 10 * 10, быстрее пары в 1,23–1,80 раза — для тех, кому не нужен кортеж из Math.DivRem;

  • на int менять нечего: там деление с остатком уже собрано в одно умножение;

  • лишнее умножение забирает часть того, что uint даёт на делении, но не всё: на разборе числа по цифрам он остаётся быстрее int — 0,81–0,86.

Проход описан в документации: Common Subexpression Elimination, код в optcse.cpp, реализация Math.DivRem — в Math.cs. Обращение открыто с августа 2025, help wanted: dotnet/runtime#119131. Замеры, отчёты и листинги: DivRemProof.

Всем удачи и до новых встреч!

Теги:
+7
Комментарии0

Добавил "галерею фишек" и документацию для акбуры, разумеется галерею написал на самом кастомном dsl

Теги:
+3
Комментарии0

Привет!

Появилась идея: локальная база знаний, строящаяся по коду проекта на вашем компе и способная человеческим языком отвечать на вопросы по проекту, помогающая джунам быстро вкатиться в суть, а синьорам бегло проверить что они сломают очередным обновлением. Интересно ли было вам таким пользоваться? Помогло ли бы оно?

Поделитесь в комментариях)

Upd. Спасибо за комментарий, важная оговорка: проект работает локально на вашем компе, без утечек в инет)

Теги:
+5
Комментарии13

Первая версия генератора кода готова. Сделал я его через IIncrementalGenerator и она очень сильно не оптимизирована, но да ладно он мне все ровно нужен был только ради следующей статьи про диагностику dsl

Теги:
+4
Комментарии0

Прогнал официальный conformance suite OpenID Foundation против redb.Identity.

Basic OP: 35 модулей, 0 failures. Config OP: 0 failures.

Что нашли и исправили:

  • PII утечка: scope-derived claims (phone, email) попадали в id_token — они должны быть только в UserInfo

  • UserInfo возвращал внутренности OpenIddict через deny-list

  • Отсутствовал Cache-Control: no-store на token endpoint (RFC 6749 §5.1)

  • prompt=login / max_age ре-аутентификация была сломана

Одно WARNING осталось намеренно — два приватных claim в id_token с обоснованием.

Полный отчёт

Теги:
+2
Комментарии0

Cтaтья «BotSharp изнyтpи: ищeм cлaбыe мecтa в кoдe ИИ‑плaтфopмы нa.NET»

Пpинятo cчитaть, чтo в AI и ML бeз Python никyдa, a.NET — этo иcключитeльнo иcтopия пpo enterprise, вeб‑paзpaбoткy и гeймдeв. Ho пpoeкт BotSharp гoтoв пocпopить c этим cтepeoтипoм, пpeдлaгaя ИИ‑плaтфopмy нa экocиcтeмe Microsoft.

Mы peшили зaглянyть пoд кaпoт BotSharp и пpoвepить, какие ошибки есть в его иcxoдном кoде.

Теги:
Всего голосов 4: ↑4 и ↓0+7
Комментарии0

Облачный сервис с безграничными возможностями: Бот или не бот - вот в чем вопрос.

Друзьям и коллегам, привет!

Недавно опубликовал на Хабр новую статью с разбором моего Web3-мессенджера. В ней описал личный кейс: как внедрить децентрализованные Web3 технологии и монетизировать внутренние функции приложения без подключения классических платежных систем. Почитать можно здесь: https://habr.com/ru/articles/1052088/

Дополняя тему мессенджера, хочу поделиться нововведением, которое значительно расширило возможности моего приложения. Надеюсь это будет полезно и другим разработчикам.

Создав так называемых "юзер-ботов" (user-bots), я снабдил платформу дополнительными функциями. В моей реализации такой "Бот" - это полноценный внешний сервис, который имеет точку входа - Endpoint на стороннем облаке и строгий протокол взаимодействия через его API.

В таких популярных приложениях как Telegram или WhatsApp, боты представляют собой отдельные сущности, которые привязываются к основному аккаунту пользователя. Кстати, вопреки расхожему мнению, один аккаунт в Telegram может создать через BotFather не безграничное число ботов, а строго до 20 штук (до 40 для Premium-пользователей).

В своей архитектуре я пошел совершенно другим путем. В моей схеме "Бот" по своей сути - это тот же самый пользователь, только виртуальный. Такой подход позволяет в любой момент переключить обычного юзера в бота и наоборот одним кликом.

пример бота реализующего сервис загрузки музыкального контента артиста на радио
пример бота реализующего сервис загрузки музыкального контента артиста на радио

Хотя при таком методе не получится создать «армию ботов» на один аккаунт. На каждого бота придется заводить новый. Но с точки зрения безопасности и чистоты архитектуры плюсы очевидны:

  • Полный контроль и безопасность окружения: бот изолирован и работает строго в рамках правил реального пользователя.

  • Защита от фишинга: это минимизирует риски мошенничества, делая бота реальным, полноценным аккаунтом, а не простой сервисной записью в базе данных.

  • Готовая монетизация: в профиле каждого пользователя уже прописаны платежные данные в виде Web3-кошелька. Соответственно, они автоматически доступны и боту, если тот реализует платные услуги.

РЕАЛИЗАЦИЯ НА БЭКЕНДЕ

Ниже упрощенный код класса реализующий передачу сообщений на Endpoint:

public async Task<string> ExecuteCommand(string cmd, string uid, string token, string url)
{
    using var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new ("Bearer", token);
    
    var body = new StringContent(JsonConvert.SerializeObject(new { data = cmd, user_id = uid }), Encoding.UTF8, "application/json");
    var res = await client.PostAsync(url, body);
    var json = JObject.Parse(await res.Content.ReadAsStringAsync());

    return json["success"]?.Value<bool>() == true 
        ? json["data"]?.ToString() 
        : $"Error: {json["error"]}";
}  

Рабочий шаблон внешнего Endpoint сервиса:

<?php
session_start();
header('Content-Type: text/html; charset=utf-8');

//AUTHENTICATION BEGIN

$allowedToken   = 'super-secret-token';
$providedToken  = '';

if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
    if (preg_match('/Bearer\s+(.+)/i', $_SERVER['HTTP_AUTHORIZATION'], $m)) {
        $providedToken = trim($m[1]);
    }
}

if (empty($providedToken) || $providedToken !== $allowedToken) {
    echo json_encode(['success' => false, 'error' => 'error token']);
    exit;
}

//AUTHENTICATION END

$rawInput = file_get_contents('php://input');
$userData = json_decode($rawInput, true); 

echo json_encode(['success' => true, 'data' => 'Bot answer: ' . $userData['data'] ?? null]);

?>

ВЫВОД

Используя описанную концепцию, можно в теории реализовать любой сервис не изменяя при этом основное приложение. Всё что нужно сделать, построить бот!

Очевидно, что для практического применения такого подхода необходимо обеспечить высокий уровень безопасности: внедрить защиту от SSRF (через валидацию URL и фильтрацию локальных хостов), настроить лимиты для предотвращения DoS-атак (проверяя размер заголовков и длину полученной строки), и так далее.

Всем успехов в разработке!

Благодарю за внимание.

Теги:
Всего голосов 2: ↑2 и ↓0+4
Комментарии0

SQLite-провайдер для RedBase — скоро.

Тот же API что PostgreSQL и MSSQL. Без миграций, полный LINQ, типизированные колонки.

Free: нативное расширение (.so / .dll / .dylib).
Pro: чистый C# — работает в Blazor WASM.

Минимальная версия SQLite 3.44.0+.

Теги:
Всего голосов 2: ↑1 и ↓1+2
Комментарии0

Ближайшие события

впечатлялся )))
впечатлялся )))

Сегодня к вечеру я совсем обленился и решил доверить нейронке storege создать
к коннектору в библиотеку redb.route используя redb
Изучала дольше чем писала. 😊 Накидала в одну сессию за один проход с тестами.
__
Аудитория: разработчики уже подключают DSL-маршруты к redb.Route, которые хотят, чтобы LLM был полноценным пользователем конвейера - с памятью, бюджетом, разрешениями и аудиторским журналом, а не HTTP—вызовом без сохранения состояния, который ничего не оставляет после себя.
единый линейный маршрут.Услуги.AddRedbLlmStorage() — переключает цикл работы агента с "забывает все при перезапуске" на постоянную систему по умолчанию. Все пять поверхностей (расшифровки, утверждения, бюджеты, идемпотентность, аудит) перемещаются в redb. Ни одна строка кода маршрута не меняется — ваш существующий .To("llm://claude") начинает сохраняться сам по себе.

Теги:
Всего голосов 5: ↑3 и ↓2+3
Комментарии0

Всем привет.

У меня два вопросах к подобным мне разработчикам open source, живущим в России.

  1. Как вы осуществляете поддержку своих проектов?

    Я - через github и свой дискордик. Не похоже, чтобы блокировка последнего нанесла сильный удар по русскоязычной аудитории. Я наблюдаю целые русскоязычные сервера, которые никуда не делись. И даже не похоже, чтобы поубавили в активности.
    Впрочем, справедливости ради, один человек обратился ко мне за саппортом через steam, поскольку через дискорд не мог.
    В общем, переформулируя вопрос: добавили ли вы новые каналы поддержки после официальной блокировки дискорда?

  2. Как вы принимаете донаты?

    Я - через бусти и telegram wallet. Интересно, какие есть ещё простые пути?

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии2

В фильме Пассажиры 2016 г. впервые показано, как ии-ассистент сливает чувствительную информацию другим пользователям и рушит их жизни

ИИ-агент готовится слить твой секрет другому пользователю
ИИ-агент готовится слить твой секрет другому пользователю

Прикольный эпизод из фильма Пассажиры 2016 г., который точно описывает один из механизмов работы агентов. По сюжету, герой по ошибке пробуждается один из 5000 человек на корабле, который летит на далекую планету, и понимает, что он проснулся слишком рано, а до пункта назначения лететь еще 90 лет. Единственный его собеседник - андроид-бармен Артур.

Героя мучает совесть, но он все же будит спящую пассажирку, чтобы ему было не так скучно лететь. Однако прежде чем ее разбудить, он просит Артура хранить в секрете то, что это он ее разбудил. Говоря в терминологии ии-агентов, пользователь и агент на этом месте заключили контракт о чувствительной информации.

Затем во время празднования ДР героини в баре она сообщает Артуру, что между ними нет секретов. Артур, как хороший ии-агент, переспрашивает у героя, так ли это, и тот подтверждает, не особо задумываясь. В этот момент ии-агент получает указание, что эта информация больше не является чувствительной, что сразу же рушит счастье героя. Пардон за спойлер, если что.

Телеграм канал автора, где он что‑то пишет про ML, NLP и разработку

Теги:
Всего голосов 7: ↑7 и ↓0+7
Комментарии0

Привет! Если мы еще не знакомы - я пишу в основном о том, как попытаться определить грейд разработчика не привлекая его на множество собеседований.
Если Вы - .Net-разработчик, буду признательна, если пройдете этот опрос с небольшим заданием на кодинг. Ваши ответы позволят мне понять, правильно ли я двигаюсь и верные ли инструменты использую. Чем больше ваших ответов - тем проще будет мне)
Заранее спасибо!

P. S. Прекращаю рассылку ответов, спасибо всем, кто участвовал! Но если хотите помочь исследованию - буду благодарна за новых респондентов) (можно написать в комментарии, если хотите получить результаты, но проверяйте, что доступ для сообщений открыт)

Теги:
Всего голосов 3: ↑2 и ↓1+1
Комментарии1

ИИ что? Проверяем Semantic Kernel

Проекты, связанные с интеграцией искусственного интеллекта, всё чаще становятся частью повседневной разработки. Один из таких проектов — Semantic Kernel. Он представляет собой SDK для построения AI-агентов и оркестрации LLM-сценариев и активно развивается компанией Microsoft.

Однако под капотом даже самых современных решений скрывается вполне обычный C# код со всеми присущими ему проблемами. Поэтому мы проверили проект и написали статью о самых интересных ошибках в коде Semantic Kernel.

Теги:
Всего голосов 3: ↑3 и ↓0+4
Комментарии0

Статья #2: Сказ о том, как Dictionary дырявым стал, или Почему Пол «потерял» данные

Действующие лица:

  • Пол (МП): Год в индустрии, MacBook в наклейках, верит в магию фреймворков и то, что .NET сам всё порешает за его спиной.

  • Дядя Паша (ДП): 47 лет, архитектор старой закалки. Помнит времена, когда память выделяли дескрипторами, а за неэффективный алгоритм могли и из проекта попросить. Пьет Мальбек, смотрит на Пола как на жертву современного маркетинга.

Диалог

Пол: — Дядь Паш, ну это издевательство! .NET точно дырявый. У меня Dictionary<UserContext, string>, я туда записываю данные, а через секунду стучусь по точно такому же ключу — и KeyNotFoundException! Я дебажил три часа: поля в объекте идентичны, ID совпадает до бита. Где мои данные? Они что, протухли?

ДП: (медленно отрезает кусок эмпанады и смотрит на Пола с плохо скрываемой жалостью) — Эх, Пол... Жертва ты «быстрых курсов за 30 дней». Тебя там научили кнопки нажимать, а как шестеренки внутри хрустят — забыли. Ты мне скажи, соколик, как твой Dictionary поймет, что это один и тот же ключ, если ты ему каждый раз подсовываешь новый адрес в памяти?

Пол: — Ну как... Поля же одинаковые! UserId, TenantId. Разве он не внутрь объекта смотрит?

ДП: — Внутрь он посмотрит, когда ты его заставишь. А пока твой class UserContext — это ссылочный тип. Для рантайма твои два объекта — это как две одинаковые бутылки вина: этикетки одни, а пробки разные. Ты один объект в словарь положил, адрес его запомнил, а потом пришел с другим адресом. Для Dictionary это разные ключи. Он идет искать в другую «корзину» (bucket) и, естественно, находит там только дырку от бублика.

Пол: — И что теперь? Опять писать эту бесконечную портянку: Equals, GetHashCode, проверять на null, комбинировать поля? Это же прошлый век!

ДП: (прищуривается, делает глоток Мальбека) — Слушай сюда, инженер «счастливого будущего». В C# есть record. И это не просто «синтаксический сахар» для ленивых.

Запомни, Пол: Record — это архитектурный контракт на Value-based equality.

Когда ты пишешь public record UserContext(int Id), компилятор сам, за тебя, пишет правильный GetHashCode, который считается по значениям полей, а не по адресу. И Equals он пишет такой же. Для Dictionary два разных инстанса одного рекорда с одинаковыми данными будут одним и тем же ключом. Понимаешь? Одна строчка кода закрывает проблему, на которой вы жжете тысячи долларов облачного бюджета.

Пол: — Ладно, с «исчезновением» понятно. Но это же просто удобство, так? На производительность-то это как влияет?

ДП: (отставляет бокал и смотрит на Пола, как на человека, утверждающего, что старый фермерский пикап и болид Формулы-1 едут одинаково, потому что у обоих по четыре колеса)

— «Просто удобство»? Эх, Пол... Ты хоть раз заглядывал под капот? В реальном проекте, когда ты меняешь свой тяжелый класс на компактный record, скорость поиска в Dictionary взлетает в три раза. Минимум!

Пол: — В три раза?! Да ну, за счет чего? Это же тот же самый поиск в хэш-таблице!

ДП: — За счет инженерной точности. Компилятор генерирует для рекорда максимально эффективный код: без лишних кастов и с идеально подогнанным вычислением хэша. Твой рекорд залетает в пазы Dictionary как деталь от швейцарских часов, а твой класс — как ржавый болт, который нужно еще полчаса подпиливать напильником.

Пол: — Слушай, дядь Паш... Я ведь реально думал, что Dictionary — это просто. А тут — магия адресов, хэш-коллизии, контракты рекордов... Откуда ты всё это знаешь?

ДП: — Я это не помню, я это знаю. Потому что в наше время учили строить фундамент, а не клеить обои на гнилые стены. Современные курсы тебе этого не скажут — им выгодно, чтобы ты бесконечно покупал их «продвинутые уровни».

— Хочешь знать, где еще у тебя «дыры»? Я систему собрал. Там 15+ тысяч вопросов, которые вытрясут из тебя всю дурь и покажут, где ты реально профи, а где просто заголовки на Medium читал.

— Называется «Я хочу знать .NET». Ссылка у тебя есть — iwanttoknow.net. Пройдешь раздел по коллекциям без единого мата — налью тебе Мальбека. А пока — иди, переделывай.

Теги:
Всего голосов 14: ↑1 и ↓13-12
Комментарии1

Всем хабровчанам удачной недели!

Хотел поинтересоваться такой темой как школа «Result/University». Кто обучался, как быстро удалось найти работу? Какова оценка по 5 шкале?

Смогут ли ребята ввести в данную тему с минимальными рисками?

Теги:
Всего голосов 2: ↑0 и ↓2-2
Комментарии21