• Как правильно инициализировать библиотеку классов в .NET?

    @romaro Автор вопроса
    Ну так DI то у меня доступен исключительно на уровне приложения. Или ты предлагаешь все элементы из Lib.Controls проинициализировать в корне композиции и запихнуть DI?

    Хорошо, я создам класс команды и помещу его в контейнер, передав зависимость от сервиса этикеток через конструктор (возможно DI сам будет инициализировать):
    ...
                // Command
                var printCommand = new ShowCargoLabelPrintWindowCommand(labelService);
    
                var host = Host.CreateDefaultBuilder()
                .ConfigureServices(services =>
                {
    ...
                    services.AddSingleton<ShowCargoLabelPrintWindowCommand>(printCommand);
                })
                .Build();


    Но как быть с визуальными компонентами? Например, у меня в Lib.Controls есть форма, а не ней кнопка, по которой должна вызываться данная команда. Как же я смогу извлечь для нее команду из контейнера, если библиотека ничего не знает о DI, который собран на стороне приложения?
    <Button Command="{Binding Source={x:Static c:CommonCommands.ShowCargoLabelPrintWindowCommand}}" CommandParameter="{Binding Cargo}" Content="Печать этикетки грузоместа" Margin="5" Padding="0,5" Style="{DynamicResource printCargoLabelButtonStyle}"/>


    Я планировал преобразовать фабрику CommonControls в сервис команд, инициализировать этот сервис в корне и так же поместить в DI, а затем, в библиотеке, получать ссылку на нужную команду таким образом:
    <Button Command="{Binding Source={x:Static local:ApplicationHost.GetCommand<ShowCargoLabelPrintWindowCommand>()}}" CommandParameter="{Binding Cargo}"/>


    То есть библиотеке в любом случае нужен ApplicationHost или что-то в этом роде.

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

    Но это выглядит громоздко... Я действительно хочу понять, как можно (и стоит ли) избавляться от передачи зависимостей через ApplicationHost.
    Написано
  • Как называть методы, которые не обрабатывают исключений?

    @romaro Автор вопроса
    Вот и гугл мне ничего не находит. Но привычка акцентировать внимание на таких методах уже сформировалась. Пока буду выпиливать приставку Try, чтобы не нарушать устоявшийся паттерн, и заменять на атрибут:
    [AttributeUsage(AttributeTargets.Method)]
        public class ThrowableAttribute : Attribute
        {
        }
    Написано
  • Как правильно называть такую композицию классов?

    @romaro Автор вопроса
    Наверное, тут нужно пояснить, что я работаю с фреймворком WinForms. Давайте на конкретном примере. У меня в UI-библиотеке есть компонент, который выглядит следующим образом:
    64f219cc44905540595566.jpeg
    По кнопке [+] должен открываться другая форма для создания конкретного объекта. Но класс этой формы, естественно, описан в самом приложении, библиотека о нем ничего не знает. Поэтому мне кажется, что приемлемым был бы такой код для контрола:
    void BtnCreate_Click(object? sender, EventArgs e)
            {
                /// Получаю интерфейсную ссылку на экземпляр формы. Для этого передаю
                /// в метод ссылку на вью-модель (_ctx). Конечно, я мог бы во все вью-
                /// модели запихнуть логику получения форм, но это бы грубо нарушило
                /// принцип единой ответственности для вью-моделей.
                using IDependableForm form = FormsFactory.GetCreateForm(_ctx);
                /// Подписываюсь на событие закрытия формы, чтобы определенным
                /// образом изменить состояние компонента.
                form.OnFormClosing += DependableForm_Closing;
                /// Показываю форму модально, чтобы не усложнять приложение попыткой
                /// отследить родителя.
                form.ShowAsModal();
            }


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

    Возможно, этот вариант паттерна было бы уместно назвать "статический декоратор" и его особенность в том, что декоратор создается прежде декорируемого им объекта, который вообще никогда может не появиться.

    Или более правильным будет "реверсивный декоратор", поскольку суть его в том, что часть приложения фактически передается в управление библиотеке.
    Написано
  • Почему WinForms все равно обращается к свойству, которое скрыто через new?

    @romaro Автор вопроса
    Хех) я свойство-то своей команды приватным оставил. Похоже в этом причина. Сейчас проверю.
    Написано
  • В чем может быть причина бага при асинхронной загрузке формы?

    @romaro Автор вопроса
    Дмитрий, спасибо за ценную ссылку, я переписал тестовый проект в соответствии с шаблоном, который приводится в конце статьи.
    Написано
  • Возможно ли на C# выполнить перевод криптовалюты и её трату?

    @romaro
    Есть расширение для NET, но похоже не особо обновляется https://github.com/ccxt-net/ccxt.net
    Написано
  • Как лучше хранить денежные суммы в Postgres?

    @romaro Автор вопроса
    Тоже пока выбрал этот вариант. Отпишусь, если будут проблемы. Пока вижу сложность лишь в том, что при делении сумм доли копеек всегда будут отбрасываться. Например, 23 копейки, деленные на 3, вернут по 7 копеек:
    SELECT 23/3;

    Получается, что 2 копейки стали доходом от деления, их нужно как-то учитывать... Можно снизить этот эффект, если увеличиться кратность расчетных единиц, но проблема с излишками все равно останется. Как вы это решали?
    Написано
  • Имеет ли смысл использовать здесь using для освобождения ресурсов?

    @romaro Автор вопроса
    Василий Банников, получается, что using имеет смысл только для форм, которые открываются в модальном режиме? Т.к. ход метода прерывается до закрытия формы и, следовательно, преждевременного уничтожения не произойдет.

    Хотя... я сейчас убрал using из ShowDataRecordForm и вижу, что событие Disposed, на котором у меня висит обработчик, где я отписываюсь от более длительно живущих объектов, все равно вызывается.

    Получается, фреймворк самостоятельно освобождает форму при закрытии и можно вообще не заморачиваться с using при создании форм?

    void InputContainerFormControl_Disposed(object? sender, EventArgs e)
            {
                if (_ctx != null)
                {
                    _ctx.FormContextChangedByUser -= Ctx_FormContextChangedByUser;
                }
            }
    Написано
  • Почему arw формат, сохраненный в psd, начинает весить в 5-7 раз больше?

    @romaro Автор вопроса
    Хотелось бы оставить индексацию в исходном файле, чтобы была возможность применять raw-фильтр. Или это одно с другим не связано?
    Написано
  • Как настроить рабочую среду для установки расширения pgTap?

    @romaro Автор вопроса
    Melkij, да, похоже я вчера невнимательно посмотрел, а то мог бы найти этот пакет. Тем не менее, я все-таки добил установку расширения из исходников, что помогло лучше понять, как это работает.

    В моем случае нужно было вначале установить perl:
    yum install perl

    Затем его модуль для запуска скриптов (которого почему-то не оказалось в предыдущем пакете):
    dnf --enablerepo=ol9_codeready_builder install perl-IPC-Run


    После этого я смог выполнить make install, но для проверки инсталляции пришлось так же поставить пакет контрибьютора:
    yum install postgresql15-contrib

    В общем, я стал чуточку ближе к тому, чтобы оформить микрофреймворк, который сейчас пишу на PlpgSQL в виде расширения.
    Написано
  • Как настроить рабочую среду для установки расширения pgTap?

    @romaro Автор вопроса
    Честно сказать, так и не нагугли, как активировать -devel. Но вот я пробую установить postgresql15-devel и получаю ошибку:
    [root@pg-dev pgtap-1.2.0]# yum install postgresql14-devel
    Last metadata expiration check: 15:53:08 ago on Thu Apr 27 00:40:38 2023.
    Package postgresql14-devel-14.7-1PGDG.rhel9.x86_64 is already installed.
    Dependencies resolved.
    Nothing to do.
    Complete!
    [root@pg-dev pgtap-1.2.0]# yum install postgresql15-devel
    Last metadata expiration check: 15:53:19 ago on Thu Apr 27 00:40:38 2023.
    Error:
     Problem: cannot install the best candidate for the job
      - nothing provides perl(IPC::Run) needed by postgresql15-devel-15.2-1PGDG.rhel9.x86_64
    (try to add '--skip-broken' to skip uninstallable packages or '--nobest' to use not only best candidate packages)
    [root@pg-dev pgtap-1.2.0]#


    Как видите, я вчера по ошибке установил postgresql14-devel.

    Я правильно понимаю, что от меня сейчас требует обновить модуль perl и после установки пакета для 15 версии нужная мне директория должна будет появиться?
    Написано
  • Как я могу сохранить список comboBox1, после закрытия программы?

    @romaro
    twobomb, я просто его использую активно в приложении для сериализации данных, отправляемых на сервер. Поэтому спрашиваю без всякого сарказма: в чем там проблема? По скорости, если верить обзорам, она даже чуть быстрее работает, чем newtonsoft. В .NET 8 его вроде бы еще допилили.
  • Как я могу сохранить список comboBox1, после закрытия программы?

    @romaro
    twobomb, кстати, для сериализации в json сторонняя библиотека уже не очень-то и нужна, есть нативное решение.
  • В чем может быть проблема с диском Seagate?

    @romaro Автор вопроса
    Ну у меня 5 лет не отваливался... Драйвера не обновлял. Просто пропал и все. Просто смущает, что Кристал рапортует об отсутствии проблем. Я думаю попробовать залезть на него через r ковер, но насколько вероятно, что при текущих показателях сканирование этим софтом усугубит проблему?
  • В каком виде хранить локальный кеш реляционной структуры данных?

    @romaro Автор вопроса
    Román Mirilaczvili, ну хотя бы ради отзывчивости интерфейса. Например, есть таблица. У таблица колонки. Названия колонок из базы приходят в виде productName, а нужно, чтобы отобразилось "Название товара". Можно при каждой загрузке грида отправлять запрос в БД на метаданные, или получать дополнительные поля в каждом запросе, а можно (что значительно быстрее и экономнее по ресурсам) обращаться к кешу. Так же можно кешировать права доступа. Вместо того, чтобы перед открытием пункта меню запршивать базу, а есть ли у пользователя права на работу с данным объектом, достаточно обратиться к кешу этих прав. Естественно, права все равно будут проверены при загрузке объекта. Но кеш позволит даже не показывать пользователю условный пункт меню, клик по которому должен быть обработан как загрузка этого объекта.
  • В каком виде хранить локальный кеш реляционной структуры данных?

    @romaro Автор вопроса
    Василий Банников, ну в самом приложении я как раз буду работать со строгой типизацией. Датасет поле вообще будет приватным, доступ к метаданным только через фабричные методы MetadataRepository, которые возвращают объекты.

    Идея DataSet в том, чтобы хранить кеш в "сыром" виде, а объекты конструировать только по требованию.
  • В каком виде хранить локальный кеш реляционной структуры данных?

    @romaro Автор вопроса
    Да, и еще такой момент. Я до конца не знаю, как метаданные будут использоваться. Приложение достаточно объемное и могут появиться непредвиденные кейсы. Если сразу начинать со справочников, то может оказаться, что какие-то из них не оптимальны. Например, часть сущностей удобнее искать не по первичному ключу, а по имени. А с датасетами только фабричный метод поправить.
  • В каком виде хранить локальный кеш реляционной структуры данных?

    @romaro Автор вопроса
    Василий Банников, я как рассуждаю. Данные ведь изначально приходят из реляционной БД, а значит собрать датасет плевое дело: через npgsql получаю готовые инстансы DataTable, остается добавить их в DataSet.

    Я сейчас склоняюсь к тому, чтобы для виндового приложения:
    - создать статический MetadataRepository
    - хранить датасет со всеми таблицами в одном статическом поле
    - объявить типы GuiViewObject, GuiViewColumnObject и т.д.
    - создать фабричные методы вроде:
    public static GuiViewObject GetViewInfo(int id) {...}


    Внутри таких методов будет выполняться обращение к таблицам из DataSet, чтобы получить строго типизированные объекты.

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

    Плюсом (повторюсь) я вижу то, что из DataSet проще копировать данные непосредственно в реляционную базу (например, в SQLite). Обновление метаданных будет идти быстрее (не требуется собирать все объекты, некоторые из которых даже не будут запрошены пользователем за время жизненного цикла приложения).

    Минусом будет определенный оверхед на обращение к датасету, когда понадобится какой-то объект метаданных. Предполагаю, не сильно критичный, т.к. данные хранятся в оперативной памяти.

    Что думаете?
  • Как удалить событие, добавленное в виде лямбда-выражения?

    @romaro Автор вопроса
    Спасибо! Я решил пойти немного другим путем. Задача, которую нужно было решить связана с отображением контекстного меню по клику на DataGridView (winforms).

    Требуется, чтобы первый пункт меню становился бы видимым лишь в том случае, когда клик происходит по ячейке (событие CellMouseDown). Проблема заключалась в том, что обработчик этого события должен открывать форму объекта с тем идентификатором, к которому относится строка клика. Но обработчик клика ничего не знает об id, которое вычисляется при перехвате CellMouseDown. Я думал, что можно как-то изящно "замкнуть" состояние первого обработчика, а затем обнулить его, выполнив отписку.

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

    // Потребителем этой переменной является только обработчик клика, поэтому не важно, что кеш подвисает в случае клика за пределами ячейки
            int? _idCache;
            void GridControl_CellMouseDown(object? sender, DataGridViewCellMouseEventArgs e)
            {
                _idCache = null;
    
                _gridControl.CurrentCell = _gridControl[e.ColumnIndex, e.RowIndex];
                var idCell = _gridControl.CurrentRow.Cells[AppSettings.IdColumnName];
                _idCache = idCell.Value == DBNull.Value ? null : (int)idCell.Value;
    
                if (_idCache.HasValue)
                {
                    _openMenuItem.Visible = true;
                    _openMenuItem.Click -= Open_Click;
                    _openMenuItem.Click += Open_Click;
                }
            }
    
    // Открываем форму
            void Open_Click(object? sender, EventArgs e)
            {
                App.ShowDataRecordForm(_dataDomainName, _idCache);
            }
  • Ютуб-канал Simple Сode может ли научить яп C#?

    @romaro
    Ваша нативочка и на озоне в комментариях и здесь :))