Чтение XML в формат LINQ для проверки данных файла журнала. Условия в формате списка. linq не получает преобразованный XML

У меня есть проект, который считывает XML-файл ожидаемых результатов сборки для каждой области сборки и сравнивает его с журналами сборки, чтобы определить, прошла ли сборка. Прямо сейчас linq показывает каждое условие в отдельной строке результатов, но (поскольку я просматриваю каждую строку результатов linq, чтобы проверить успешность) мне нужно иметь возможность проверять оба условия (если их два) в момент в одно и то же время, а не в разное время. В противном случае я мог бы проверять не ту строку файла журнала. Обратите внимание, что иногда существует два критерия успеха, но не всегда. Это всего лишь два условия, которые мне нужно проверить сразу. Чтобы внести это изменение, я переместил тег SuccessCriteria из тега «Условия» в раздел «Условия». Благодаря этому изменению linq не воспринимает строки XML. В моем полном XML-файле должно быть около 69 строк результатов_b, но он включает пару разделов, которые я не показываю, которые я оставил в старом формате. Я попытался изменить cName = (string)p.Attribute("name") на cName = (string)b.Attribute("name") в LINQ, но получил те же результаты. В то же время было изменено значение cValue.

Мой вопрос: как мне изменить LINQ, чтобы получить полный XML-файл, и groupBy_b, чтобы проверить оба условия одновременно? Я хочу объединить условия в список, но вокруг него должны быть другие данные, такие как машина сборки, имя процесса и критерии успеха. Например, таблица, которую я помещаю в xml, для некоторых частей журнала выглядит так: ParseISLogStats (поэтому мне нужно проверить правильность BuildProject и BaseBuildArea в журнале, поскольку это разные столбцы, но критерий успеха только один для этих двух условия):

ParseISLogStats    Desctiption=nightly v17 partb   Projects=8   Status=Success
ParseISLogStats    Description=nightly v17 partb   Projects=8   Status=Success
ParseISLogStats    Description=nightly v17         Projects=33  Status=Success

Я смотрел на эти примеры: линклинк

На данный момент у меня есть этот код (с добавлением groupBy, чтобы попытаться собрать условия в список):

void ReadXml()
{
    int i_results = 0; //0 is successful for all; if it increases we have failed in one
    XmlDocument xml = new XmlDocument();
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string newPath = Path.GetFullPath(Path.Combine(path, @"..\.."));
    string finalPathXmlb = Path.GetFullPath(Path.Combine(newPath, @"BuildVerificationBuildAttributes_b.xml")); //new format

    teamPost = "";

    //linq 
    XDocument xmlDoc2 = XDocument.Load(finalPathXmlb);
    //XElement buildVerificationElement = xmlDoc1.Element("BuildVerification");
    //IEnumerable<XElement> buildElements = buildVerificationElement.Elements("build");
    //IEnumerable<XElement> buildMachines = buildElements.Elements("BuildMachine");

    //get just the times from the xml
    //var codeFreezeTime = xmlDoc1.Descendants("codeFreezeTime").First()?.Value;
    //logToFileAndScreen(codeFreezeTime);

    //get build info from xml, such as process (log name), condition, successCriteria, build machine, start time header, 
    IEnumerable<XElement> processes = buildMachines.Elements("Process");

    var results_b =
        xmlDoc2.Descendants("build")
        .SelectMany(b => b.Descendants("Process")
        .SelectMany(p => p.Descendants("Condition")
        .SelectMany(c => c.Descendants("SuccessCriteria")
        .Select(sc => new
        {
            buildMach = (string)b.Element("BuildMachine"),
            p1 = (string)p.Element("ProcessName"),
            startTimeHeader = (string)p.Element("startTimeHeader"),
            cName = (string)c.Attribute("name"),
            cValue = (string)c.Attribute("value"),
            f1 = (string)sc.Element("field"),
            c1 = (string)sc.Element("comparison"),
            v1 = (string)sc.Element("value")
        })))).ToList();
    
    var groups_b = results_b.GroupBy(x => new { build_machine = x.buildMach, process_name = x.p1, name = x.cName }).ToList();
} //ParseLogFile

Я хочу, чтобы мой groups_b выглядел так (но если linq не работает, он никогда этого не сделает, но я не думаю, что у меня все правильно в моем groups_b, если linq действительно работает):

{buildMach = mach64, p1=ParseISLogStats, startTimeHeader = StartTime, {cName=Description, cValue=nightly v17 partb, cName=Projects, cValue=8}, f1=Status, c1=equal, v1=Succcess}
{buildmach = mach64, p1=ParseISLogStats, startTimeheader = StartTime, {cName=Description, cValue = nightly v17, cName=Projects, cValue=33},f1=status, c1=equal, v1=Success}

Вот как выглядит мой XML:

<?xml version = "1.0" encoding = "UTF-8"?>
<BuildVerification>
    <codeFreezeTime>19:00</codeFreezeTime>
    <build>
        <BuildMachine>mach31</BuildMachine>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\R\filename1.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\R\filename2.cs">
                        </Condition>
                    </Conditions>
                        <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison>
                            <value>Success</value>
                        </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\R\filename3.h">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison>
                            <value>Success</value>
                    </SuccessCriteria>
            </Process>

        </build>
        
        <build>
        <BuildMachine>mach46</BuildMachine>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\filename4.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\fiename5.h">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\filename6.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison>
                            <value>Success</value>
                    </SuccessCriteria>
            </Process>
            
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\D\filename7.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\K\filename8.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\K\filename9.h">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            
                        
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\K\filename11.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> <!--can't use >= here -->
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\filename22.cs">   
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value = "\\view\Build_NightlyDeveloper\K\filename33.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            
            
            <Process>
                <ProcessName>SpinISProjectVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='ProductName' value = "P1">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinISProjectVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='ProductName' value = "P2">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinISProjectVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='ProductName' value = "P3">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>GetSWStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='DestinationPath' value = "D:\Builds\Retail\nightly">
                        </Condition>
                        <Condition
                            name='DestinationPath' value = "D:\Builds\Retail\nightly">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Successful</value>
                    </SuccessCriteria>
            </Process>
            
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='Nightly' value = "D:\nightly V17">
                            </Condition>
                        <Condition
                            name='Projects' value=33>
                            
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='Nightly' value = "D:\nightly V17 partb">
                        </Condition>
                        <Condition
                            name='Projects' value=8>
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='BuildProject' value = "DInstallerBuild">
                        </Condition>
                        <Condition
                            name='BaseBuildArea' value = "D:\R_Builds\K\v2.1\nightly">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='BuildProject' value = "KInstallerBuild">
                        </Condition>
                        <Condition
                            name='BaseBuildArea' value = "D:\R_Builds\K\v2.1\nightly">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison> 
                            <value>Success</value>
                    </SuccessCriteria>
            </Process>

        </build>
    </BuildVerification>

Обновлять: Я попытался изменить LINQ на следующее, но он по-прежнему просто возвращает данные из XML, который я оставил в старом формате, а не из того, в котором SuccessCriteria находится на том же уровне/глубине, что и Условия.

var results_b = (
    from b in xmlDoc2.Descendants("build")
    from p in b.Descendants("Process")
    from c in p.Descendants("Condition")
    from sc in c.Descendants("SuccessCriteria")
    select new
    {
        buildMach = (string)b.Element("BuildMachine"),
        p1 = (string)p.Element("ProcessName"),
        startTimeHeader = (string)p.Element("startTimeHeader"),
        cName = (string)c.Attribute("name"),
        cValue = (string)c.Attribute("value"),
        f1 = (string)sc.Element("field"),
        c1 = (string)sc.Element("comparison"),
        v1 = (string)sc.Element("value")
    })
    .ToList();

пожалуйста подтвердите. значение условия = 33 и значение = 8 - должно быть в виде строк?

Power Mouse 02.05.2024 21:19

да, они читаются как строки из xml.

Michele 02.05.2024 21:22

Несколько предыдущих вопросов, связанных с этим плакатом: здесь , здесь , здесь и здесь. Похоже, та же общая проблема, но структура данных XML изменилась. Элемент SuccessCriteria теперь является дочерним элементом Process вместо Condition. Раньше также были случаи, когда несколько одноуровневых элементов SuccessCriteria, но последний образец больше этого не показывает.

T N 02.05.2024 23:42

@TN - есть ли способ добавить сюда именованные списки вместо анонимных?

Michele 03.05.2024 16:55
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы перемещаться по XML-данным с помощью запроса LINQ, вам необходимо сначала понять иерархию элементов (схему), а затем адаптировать свой запрос для соответствия.

Из анализа вашего примера XML выяснилось, что ваш текущий контент имеет такую ​​структуру:

BuildVerification
    codeFreezeTime (text)
    *build
        BuildMachine (text)
        *Process
            ProcessName (text)
            startTimeHeader (text)
            *Conditions
                Condition
                    @name (text)
                    @value (text)
            *SuccessCriteria
                field (text)
                comparison (text)
                value (text)

(Это не формальная запись, а просто мой способ расположения вещей. Отступы отражают вложенность, «(текст)» указывает на элементы, имеющие текстовое содержимое, «@» указывает на атрибут, а «*» я использовал для обозначения элементов, которые можно повторить.)

Я отметил SuccessCriteria как повторяющийся, поскольку в ваших предыдущих вопросах были примеры, когда элемент Process содержал несколько вложенных элементов SuccessCriteria.

Итак, основная проблема вашего текущего запроса заключается в том, что вы пытаетесь выбрать элементы SuccessCriteria, которые являются дочерними элементами Condition, но если вы просмотрите приведенный выше макет, вы увидите, что этого никогда не произойдет. SuccessCriteria теперь ребенок Process.

Нам также необходимо обрабатывать вложенные коллекции.

Запрошенный вами макет результатов примерно следующий:

{
    buildMach = value,
    p1 = value,
    startTimeHeader = value,
    {
        cName = value,
        cValue = value,
        cName = value,
        cValue = value
    },
    f1 = value,
    c1 = value,
    v1 = value
}

Я считаю, что для обработки нескольких условий для каждого процесса и (потенциально) нескольких критериев для каждого процесса (которые соответствуют этим условиям) вам действительно нужен макет вроде:

List of {
    buildMach = value,
    p1 = value,
    startTimeHeader = value,
    Conditions = List of {
        cName = value,
        cValue = value
    },
    SuccessCriteria = List of {
        f1 = value,
        c1 = value,
        v1 = value
    }
}

Это позволит вам определить правила проверки, каждое из которых может определять несколько условий соответствия, а затем применять несколько проверок критериев успеха.

Чтобы получить вложенную структуру, указанную выше, части Condition и SuccessCriteria вашего запроса необходимо будет переместить в подвыражения конструктора объекта результата.

Для улучшения читаемости я также рекомендую отказаться от синтаксиса приведения (string) и вместо этого использовать свойство .Value для получения содержимого элемента и атрибута. (Если есть вероятность, что элемент или атрибут может отсутствовать или иметь значение NULL, вы можете использовать синтаксис ?.Value, распространяющий значение NULL.)

Результирующий код будет примерно таким:

var results1 =
    xmlDoc2.Descendants("build")
    .SelectMany(b => b.Descendants("Process")
    .Select(p => new
    {
        buildMach = b.Element("BuildMachine").Value,
        p1 = p.Element("ProcessName").Value,
        startTimeHeader = p.Element("startTimeHeader").Value,
        conditions = p.Descendants("Condition")
            .Select(c => new
            {
                cName = c.Attribute("name").Value,
                cValue = c.Attribute("value").Value
            })
            .ToList(),
        successCriteria = p.Descendants("SuccessCriteria")
            .Select(sc => new
            {
                f1 = sc.Element("field").Value,
                c1 = sc.Element("comparison").Value,
                v1 = sc.Element("value").Value
            })
            .ToList()
    })).ToList();

Эквивалент с использованием синтаксиса запроса LINQ:

var results2 = (
    from b in xmlDoc2.Descendants("build")
    from p in b.Descendants("Process")
    select new {
        buildMach = b.Element("BuildMachine").Value,
        p1 = p.Element("ProcessName").Value,
        startTimeHeader = p.Element("startTimeHeader").Value,
        conditions = (
            from c in p.Descendants("Condition")
            select new
            {
                cName = c.Attribute("name").Value,
                cValue = c.Attribute("value").Value
            })
            .ToList(),
        successCriteria = (
            from sc in p.Descendants("SuccessCriteria")
            select new
            {
                f1 = sc.Element("field").Value,
                c1 = sc.Element("comparison").Value,
                v1 = sc.Element("value").Value
            })
            .ToList()
    })
    .ToList();

Результаты (частичные):

...
{"buildMach":"mach46","p1":"ParseISLogStats","startTimeHeader":"StartTime","conditions":[{"cName":"BuildProject","cValue":"DInstallerBuild"},{"cName":"BaseBuildArea","cValue":"D:\\R_Builds\\K\\v2.1\\nightly"}],"successCriteria":[{"f1":"Status","c1":"equal","v1":"Success"}]}
{"buildMach":"mach46","p1":"ParseISLogStats","startTimeHeader":"StartTime","conditions":[{"cName":"BuildProject","cValue":"KInstallerBuild"},{"cName":"BaseBuildArea","cValue":"D:\\R_Builds\\K\\v2.1\\nightly"}],"successCriteria":[{"f1":"Status","c1":"equal","v1":"Success"}]}

См. эту .NET Fiddle.

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

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

Большое спасибо! Я пробую первый и использовал значение ?.Value, потому что у меня есть пара значений, для которых я использую "". Теперь мне просто нужно выяснить, как выполнить цикл foreach со строками результатов для отправки в мой метод синтаксического анализа с объектом списка. Я действительно ценю твою помощь. Я пытался понять это всю неделю. Ваша запись очень полезна, и теперь я понимаю это намного лучше.

Michele 03.05.2024 14:16

Я пытаюсь использовать вложенный список для отправки списка условий в мой метод int ParseLogFile1 (строка codeFreezeTime, строка startTimeHeader, строка serverName, строка logName, условия List<List<string>>, List<List<string>> SuccessCriteria) {} и это при вызове метода: foreach (var cur in results1) { i_results += ParseLogFile1(codeFreezeTime, cur.startTimeHeader, cur.buildMach, currentItem.p1, cur.conditions, cur.successCriteria); } но возникла ошибка преобразования анонимного типа

Michele 03.05.2024 15:21

Если вам нужно передавать эти данные между функциями, вам следует определить именованные классы для Condition, SuccessCriteria и всего ProcessValidationRule (или любого другого имени класса, которое вам понятно), каждый с соответствующими свойствами. Коллекции Condition и SuccessCriteria внутри класса ProcessValidationRule будут иметь типы List<Condition> и List<SuccessCriteria> соответственно. Приведенный выше запрос затем создаст новые и заполнит эти именованные классы вместо текущих безымянных/анонимных классов.

T N 03.05.2024 18:05

Другие вопросы по теме