C#, ConfigurationBuilder и типизированные значения

я не понимаю, почему при стандартном чтении json-файла с конфигурацией параметры конфиграции приходят мне как строки, независимо от того, как они написаны в json-файле.

Поясню.

Вот я создаю стандартный консольный проект на NET Core 8, добавляю туда зависимости Microsoft.Extensions.Configuration и Microsoft.Extensions.Configuration.Json

Затем прямов Main'е я пишу

    var builder = new ConfigurationBuilder().AddJsonFile("appSettings.json", false, false);
    var configuration = builder.Build();

    var rc = configuration["retryCounter"];

Затем я добавляю appSettings.json, в котором написано

{
    "retryCounter": 42
}

... и в коде я получаю строку со значением "42":

строка

И вот чего я не понимаю:

  1. json у нас - типизированный. Да, там есть только примитивные типы, но число

     "retryCounter": 42    
    

отличатеся от строки

    "retryCounter": "42"
  1. Если я буду пользоваться своим классом, каким нибудь MySettings.cs с описанными в нём полями, и буду его сериализовать - десериализовать в json - то у меня будет преобразование в json и обратно, сохраняющее элементарные типы (int, string, float)

  2. В то же время, использование конфигурации и ConfigurationBuilder'а - это рекомендуемый путь. То есть, если ты делаешь не так - то тебе в приличном месте дадут по рукам.

Так почему же тогда этот рекомендуемый путь - такой ограниченный, и не отличается от работы с ini- файлом? Здесь есть какой то смысл, которого я не понимаю, или это просто "все данные в мире - это строки, и не надо нам морочить голову!"?


Ответы (1 шт):

Автор решения: Andrei Khotko

Кратко

Причины такого подхода работы с конфигурацией (на мой взгляд):

  1. Универсальность. В такую структуру данных можно поместить конфигурацию любого формата, нужно лишь правильно преобразовать изначальный формат в словарь.
  2. Строгая типизация языка C# накладывает ограничения на хранение разнотипных данных в одном словаре. Придется либо в ущерб производительности использовать общий тип object для хранения значений, что приведет к boxing / unboxing значений конфигурации, либо придется изменить способ хранения данных, что может "ударить" по универсальности.

Подробнее

Внутри ConfigurationBuilder метод .Build() возвращает ConfigurationRoot, который в свою очередь использует доступные ConfigurationProvider для получения значений конфигурации.

ConfigurationProvider устроен таким образом, что данные конфигурации хранятся как словарь с параметрами "ключ-строка", или Dictionary<string, string?> Поскольку C# - строго типизированный язык, то при желании хранить данные с нужной типизацией для элементарных типов придется воспользоваться Dictionary<string, object?>, и при вытаскивании данных передавать тип, который мы ожидаем на выходе (либо использовать dynamic). Выходит не очень практично, да и есть накладные расходы на boxing / unboxing. Ведь всем хочется получать конфигурацию максимально быстро и потребляя меньше памяти.

Также, ConfigurationProvider - базовый абстрактный класс, который переиспользуется в более конкретных реализациях. Идея хранить все значения как пара "ключ - строка" - вполне разумная для строго типизированного языка программирования. Мы получили строгую плоскую структуру данных, где доступ к универсальному строковому значению можно получить по композитному ключу (который напоминает ключи redis cache). Структура простая, и мы получаем общий интерфейс работы с конфигурацией, который можно применять при любых других способах получения конфигурации, не только JSON (например, из текстового файла, из БД и др.). В каком-нибудь Javascript, где типизация динамическая, хранить значения с оригинальным типом было бы уместно.

→ Ссылка