<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>C# Magazine</title><subtitle>Заметки о коде</subtitle><author><name>C# Magazine</name></author><id>https://teletype.in/atom/csharpmagazine</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/csharpmagazine?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@csharpmagazine?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=csharpmagazine"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/csharpmagazine?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-07T05:34:48.953Z</updated><entry><id>csharpmagazine:k-komanda</id><link rel="alternate" type="text/html" href="https://teletype.in/@csharpmagazine/k-komanda?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=csharpmagazine"></link><title>К - команда</title><published>2025-06-29T17:13:29.830Z</published><updated>2025-06-30T12:13:55.709Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/6c/50/6c50424f-241f-4987-94a5-c659e062e1f7.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/69/75/697542d7-1ba3-4581-822b-05ce5736219b.jpeg&quot;&gt;Что самое главное в любой организации? Конечно же люди. В этой статье расскажем о самом главном - о людях команды Iron Programmer и нашем совместном отдыхе.</summary><content type="html">
  &lt;p id=&quot;KbUi&quot;&gt;Что самое главное в любой организации? Конечно же люди. Люди, которые увлечены своим делом, работают продуктивнее. Люди, которые работают в окружении единомышленников, близких по духу, &amp;quot;на одной волне&amp;quot;, работают в разы продуктивнее. &lt;/p&gt;
  &lt;p id=&quot;4Xii&quot;&gt;В этой статье мы расскажем о нашей команде, о людях, которые делают наши курсы такими, какими их видите вы, и немного о том, &amp;quot;как мы провели этим летом* &amp;quot;. &lt;/p&gt;
  &lt;p id=&quot;IyhX&quot;&gt;Перед тем, как начать повествование, напомним, что несколько дней команду не было слышно и видно. Мы были в горах гостеприимной и потрясающе красивой Северной Осетии. Выражаясь современным языком, это была смесь тимбилдинга с корпоративом, где мы собрались в максимальном составе, но увы не в полном, и провели неполные четверо суток. Но каких! Впрочем, начнем по порядку.&lt;/p&gt;
  &lt;p id=&quot;f0jt&quot;&gt;В современных условиях непросто собрать команду людей, не разделенных географически. По крайней мере для сферы, где удалёнка уже становится неотъемлемой частью. Поэтому удивляться тому, что у нас команда не только в разных городах располагается, но и в разных странах, конечно, не приходится. И тем не менее, мы вместе!&lt;/p&gt;
  &lt;p id=&quot;05Jh&quot;&gt;Идея встретиться в Осетии принадлежит... тоже команде. Но родилась, как полагается, из шутки. Возможно это будет неочевидно, но мы много времени проводим в обсуждениях того, как сделать курсы лучше: какие задачи упростить, какие добавить, что улучшить или вообще удалить. И вы наверняка замечали, что количество задач в курсах постоянно меняется. Это и есть результат командной работы в том числе.&lt;/p&gt;
  &lt;p id=&quot;qtNh&quot;&gt;И конечно нельзя быть всегда серьезными, не зря же выше мы упомянули про &amp;quot;быть на одной волне&amp;quot;. В какой-то момент Иосиф сказал: &amp;quot;Вот бы вас увидеть вместе&amp;quot;. Посмеялись, пошутили, а потом прозвучало заветное: &amp;quot;а давайте..&amp;quot;. И вот мы 21 июня съехались во Владикавказ, родные места Иосифа и Школы в целом. &lt;/p&gt;
  &lt;figure id=&quot;wo5I&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f6/43/f64314d3-6ed5-413f-a386-1381913a1234.jpeg&quot; width=&quot;1280&quot; /&gt;
    &lt;figcaption&gt;В ожидании прилетающих в аэропорту Владикавказа.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;d7Ph&quot;&gt;Да, современные реалии таковы, что лучшая (по нашему мнению) школа программирования родом из Владикавказа. И это должно повлиять на тех, кто считает, что программирование и IT в целом - это только про столицы. Отнюдь. Найти применение своим навыкам и знаниям можно в любой точке мира, было бы желание. &lt;/p&gt;
  &lt;p id=&quot;084D&quot;&gt;Мы ехали на машинах, добирались на поезде, летели самолетами, каждый со своими впечатлениями и приключениями. Но мы собрались, встретились и дальше были незабываемые дни, когда команда стала по-настоящему одним целым.  И даже супруги некоторых наших коллег, не побоимся этого слова, породнились с остальной командой. Привет Мише - специалисту по 1С, Артему-питонисту и великолепной творческой Кате!&lt;/p&gt;
  &lt;p id=&quot;8x6f&quot;&gt;О том, что мы видели и где мы были словами не рассказать, пером не описать. Но мы старательно фотографировали то, что могли сфотографировать и снимали на видео, то, что можно было снять, а потому чуть дальше можно будет посмотреть на нас вместе. &lt;/p&gt;
  &lt;p id=&quot;oALF&quot;&gt;Всё рассказать невозможно, равно как и показать, и уж тем более передать все эмоции, которые мы испытали. Но если коротко, то:&lt;/p&gt;
  &lt;ul id=&quot;pu7T&quot;&gt;
    &lt;li id=&quot;3Nng&quot;&gt;ходили в горы;&lt;/li&gt;
    &lt;li id=&quot;ARLJ&quot;&gt;поднимались выше облаков;&lt;/li&gt;
    &lt;li id=&quot;iSc5&quot;&gt;спускались к водопаду;&lt;/li&gt;
    &lt;li id=&quot;EvCI&quot;&gt;купались в бассейне;&lt;/li&gt;
    &lt;li id=&quot;B1RV&quot;&gt;жарили шашлыки и готовили плов;&lt;/li&gt;
    &lt;li id=&quot;Wl9K&quot;&gt;играли в настолки;&lt;/li&gt;
    &lt;li id=&quot;N7Q4&quot;&gt;дарили подарки;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;W9Tk&quot;&gt;Но и о работе не забывали: отвечали в комментариях, кое-что исправляли, невидимое глазу со стороны и самое большое рабочее мероприятие: мозговой штурм-совещание провели по развитию школы и курсов. &lt;/p&gt;
  &lt;p id=&quot;MZbv&quot;&gt;По итогу, мы знаем куда двигаться, мы знаем с кем мы работаем и мы знаем, что делать. И вас, наши любимые ученики, мы не разочаруем!&lt;/p&gt;
  &lt;p id=&quot;OPqE&quot;&gt;А теперь приоткроем завесу тайны (хотя и не тайна это вовсе) о том, кто же мы. Коротко и по делу.&lt;/p&gt;
  &lt;p id=&quot;lzGx&quot;&gt;Итак, основатель и руководитель, идейный вдохновитель, учитель и просто хороший человек - Иосиф Дзеранов.&lt;/p&gt;
  &lt;figure id=&quot;3XEK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f2/1b/f21b0d66-82b3-4125-be23-a564133ec1a3.png&quot; width=&quot;1885&quot; /&gt;
    &lt;figcaption&gt;Иосиф Дзеранов. Основатель и руководитель школы программирования IRON PROGRAMMER&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;IS3Z&quot;&gt;Мастер переговоров и ведения дел, бизнес-ассистент Иосифа Анастасия Матюшко.&lt;/p&gt;
  &lt;figure id=&quot;vW9r&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/cc/db/ccdb4e79-1a1c-4d4a-bd50-9558840dd829.png&quot; width=&quot;1761&quot; /&gt;
    &lt;figcaption&gt;Анастасия Матюшко, бизнес-ассистент&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bXUf&quot;&gt;Начальник отдела продукта: властительница курсов и повелительница модераторов - Юлия Головинская&lt;/p&gt;
  &lt;figure id=&quot;PgwY&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3c/35/3c3520ba-5a5b-4f2b-a9bd-05a3d225d3ce.jpeg&quot; width=&quot;4397&quot; /&gt;
    &lt;figcaption&gt;Юлия Головинская, Начальник отдела продукта.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zp7b&quot;&gt;Предводитель направления PRO Go. Александр Павлович&lt;/p&gt;
  &lt;figure id=&quot;RBuB&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bf/b3/bfb31115-ffd9-4426-ba56-808778321c99.png&quot; width=&quot;1943&quot; /&gt;
    &lt;figcaption&gt;Александр Павлович, руководитель направления PRO Go&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;6U7U&quot;&gt;Модератор нестандартных решений и мастер ответов на комментарии Ангелина Кемза&lt;/p&gt;
  &lt;figure id=&quot;RQ1c&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f7/d4/f7d407e2-b295-416a-b5c0-6cca7a84eba5.png&quot; width=&quot;1951&quot; /&gt;
    &lt;figcaption&gt;Ангелина Кемза, модератор Основ и Коллекций&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PDEQ&quot;&gt;Старший куратор в потоковых курсах, гроза отстающих и ошибающихся: Светлана Козырева&lt;/p&gt;
  &lt;figure id=&quot;0m0K&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/22/1a/221a5279-db3b-4166-b883-6956d6287109.png&quot; width=&quot;1855&quot; /&gt;
    &lt;figcaption&gt;Светлана Козырева, Старший куратор в потоковых курсах, куратор на курсе по ASP.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;KokL&quot;&gt;Человек мощнейшей энергетики, открывший миру курс по базам данных: Кирилл Фисенко&lt;/p&gt;
  &lt;figure id=&quot;2U6x&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1e/5b/1e5bdd73-99f5-411d-bac3-4edc1492efe0.png&quot; width=&quot;1836&quot; /&gt;
    &lt;figcaption&gt;Кирилл Фисенко, создатель и модератор курса PRO C#. Базы данных.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;EiFj&quot;&gt;Танк (в лучшем смысле этого слова), человек, способный, если и не свернуть, то покорить любую вершину, по совместительству модератор: Евгений Вишняков.&lt;/p&gt;
  &lt;figure id=&quot;qWke&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5b/52/5b524ed6-9706-4aa3-804a-f26fdd8c87a9.png&quot; width=&quot;1998&quot; /&gt;
    &lt;figcaption&gt;Евгений Вишняков. Модератор курсов.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WIxT&quot;&gt;Разработчик внутренней платформы для аналитики и куратор на потоковых курсах по ASP и Телеграм ботам: Алексей Миронов&lt;/p&gt;
  &lt;figure id=&quot;TG0l&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e5/67/e5670aea-add2-4954-92ab-0b238545ba62.png&quot; width=&quot;1840&quot; /&gt;
    &lt;figcaption&gt;Алексей Миронов. Куратор потоковых курсов Asp и Создание Телеграм ботов.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;S56Y&quot;&gt;Это мы - команда Школы IRON PROGRAMMER. К нам всегда можно обратиться с любым вопросом, касающимся обучения на наших курсах и мы обязательно ответим и поможем. &lt;/p&gt;
  &lt;p id=&quot;z57l&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;70Kf&quot;&gt;Наши контакты:&lt;/p&gt;
  &lt;ul id=&quot;XUW7&quot;&gt;
    &lt;li id=&quot;7YL5&quot;&gt;&lt;a href=&quot;https://ironprogrammer.ru&quot; target=&quot;_blank&quot;&gt;Сайт школы&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;0ijw&quot;&gt;&lt;a href=&quot;https://t.me/csharp_publics&quot; target=&quot;_blank&quot;&gt;Канал в телеграм&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;71F9&quot;&gt;&lt;a href=&quot;https://stepik.org/org/PRO_csharp&quot; target=&quot;_blank&quot;&gt;Наши курсы&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;JLlv&quot;&gt;&lt;a href=&quot;http://t.me/ironprogrammpro_bot?start=csharp_magazine&quot; target=&quot;_blank&quot;&gt;Бот, где ответят на все вопросы о наших курсах&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;h72W&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;2skE&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/69/75/697542d7-1ba3-4581-822b-05ce5736219b.jpeg&quot; width=&quot;2318&quot; /&gt;
    &lt;figcaption&gt;Команда IRON PROGRAMMER&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;blockquote id=&quot;Flpx&quot;&gt;* отсылка к фильму &amp;quot;Как я провел этим летом&amp;quot;, не опечатка! ))&lt;/blockquote&gt;

</content></entry><entry><id>csharpmagazine:union-types</id><link rel="alternate" type="text/html" href="https://teletype.in/@csharpmagazine/union-types?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=csharpmagazine"></link><title>Боремся с &quot;union types&quot;</title><published>2025-04-17T16:55:47.679Z</published><updated>2025-04-21T19:20:13.844Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/5d/b8/5db85e44-04f7-4cf9-9108-3cc1fe4a2328.png"></media:thumbnail><category term="net" label=".NET"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/e0/47/e047371c-c329-4baa-a633-41a78f55d660.png&quot;&gt;Когда чуть-чуть поднимаешь голову как разработчик, начинают встречаться на твоем пути всякие задачи, о существовании которых не всегда мог предположить...</summary><content type="html">
  &lt;p id=&quot;uuX1&quot;&gt;Когда чуть-чуть поднимаешь голову как разработчик, начинают встречаться на твоем пути всякие задачи, о существовании которых не всегда мог предположить. Часто эти проблемы вызваны сторонними разработчиками и даже не столько в своем проекте, сколько в чужом, от которого что-то зависит. Например, данные. &lt;/p&gt;
  &lt;p id=&quot;EpsG&quot;&gt;Еще интереснее, когда твой код работает, а потом, в какой-то момент, перестает или встречает наконец ту проблему, которая была заложена когда-то, кем-то, потому что технологии, которыми там пользуются, позволяют это сделать. О чем же мы говорим?&lt;/p&gt;
  &lt;h2 id=&quot;VQND&quot;&gt;Начнем, как водится с проблемы. &lt;/h2&gt;
  &lt;p id=&quot;sqYk&quot;&gt;Допустим, мы разрабатываем приложение, которое получает данные по &lt;code&gt;api &lt;/code&gt;из стороннего сервиса. Предположим (хотя это и не редкость), &lt;code&gt;api &lt;/code&gt;плохо задокументирован и ориентироваться в получаемых объектах приходится &amp;quot;на ощупь&amp;quot;. &lt;/p&gt;
  &lt;p id=&quot;MzMd&quot;&gt;Итак, мы написали модели для объектов, начинаем работать с &lt;code&gt;api &lt;/code&gt;и в какой-то момент ловим исключение (мы же не &lt;em&gt;эти&lt;/em&gt;, мы обрабатываем исключения). Смотрим в логах, а там ошибка в парсинге данных. Странно, вроде все было правильно на этапе подготовки моделей. Вникаем в проблему глубже и оказывается, что объект, который приходит в большой модели может принимать не два состояния: либо он есть, либо &lt;code&gt;null&lt;/code&gt;, а целых четыре! (тут можно подставить другое число, но принцип останется тем же). &lt;/p&gt;
  &lt;blockquote id=&quot;xO1R&quot;&gt;Справедливости ради, с точки зрения получаемого значения разницы между пустым объектом &lt;code&gt;{}&lt;/code&gt; и &lt;code&gt;null &lt;/code&gt;нет, они равнозначно игнорируются (или нет, в зависимости от конфигурации) конвертером.&lt;/blockquote&gt;
  &lt;h3 id=&quot;9YH8&quot;&gt;Вот живой пример, с которым пришлось столкнуться нашей команде в процессе разработки:&lt;/h3&gt;
  &lt;p id=&quot;bCGP&quot;&gt;Есть некая модель, отвечающая за шаги (задачи). А есть другая модель, которая отвечает за наполнение и настройку шагов (задач), называется моделька &lt;code&gt;StepSource&lt;/code&gt;. Это не особо важно в контексте рассказа, просто знайте.&lt;br /&gt;У этой модельки много есть всякого, но что нас сегодня интересует - объект &lt;code&gt;Block&lt;/code&gt;, а внутри него объект &lt;code&gt;Source&lt;/code&gt;. Пока все нормально, модель с несколькими вложениями.&lt;br /&gt;Дальше, у модели &lt;code&gt;Source &lt;/code&gt;есть свойство (поле, параметр, you-name-it) &lt;code&gt;options&lt;/code&gt;, которое: &lt;/p&gt;
  &lt;p id=&quot;ocTG&quot;&gt;1.&lt;/p&gt;
  &lt;pre id=&quot;UBWF&quot; data-lang=&quot;clike&quot;&gt;// может принимать форму массива с объектами:
&amp;quot;options&amp;quot;: [
                        {
                            &amp;quot;is_correct&amp;quot;: false,
                            &amp;quot;text&amp;quot;: &amp;quot;a + b = 5&amp;quot;,
                            &amp;quot;feedback&amp;quot;: &amp;quot;В данном случае числа будут читаться как строки и не сложатся&amp;quot;
                        },
                        {
                            &amp;quot;is_correct&amp;quot;: true,
                            &amp;quot;text&amp;quot;: &amp;quot;a + b = 23&amp;quot;,
                            &amp;quot;feedback&amp;quot;: &amp;quot;&amp;quot;
                        },
                        {
                            &amp;quot;is_correct&amp;quot;: false,
                            &amp;quot;text&amp;quot;: &amp;quot;a + b = 2&amp;quot;,
                            &amp;quot;feedback&amp;quot;: &amp;quot;Посмотрите внимательно на код&amp;quot;
                        },
                        {
                            &amp;quot;is_correct&amp;quot;: false,
                            &amp;quot;text&amp;quot;: &amp;quot;a + b = 3&amp;quot;,
                            &amp;quot;feedback&amp;quot;: &amp;quot;Посмотрите внимательно на код&amp;quot;
                        },
                        {
                            &amp;quot;is_correct&amp;quot;: false,
                            &amp;quot;text&amp;quot;: &amp;quot;a + b = 32&amp;quot;,
                            &amp;quot;feedback&amp;quot;: &amp;quot;Посмотрите внимательно на код&amp;quot;
                        },
                        {
                            &amp;quot;is_correct&amp;quot;: false,
                            &amp;quot;text&amp;quot;: &amp;quot;Будет ошибка&amp;quot;,
                            &amp;quot;feedback&amp;quot;: &amp;quot;Код верный&amp;quot;
                        }
                    ]

&lt;/pre&gt;
  &lt;p id=&quot;mYCI&quot;&gt;2.&lt;/p&gt;
  &lt;pre id=&quot;h5un&quot; data-lang=&quot;clike&quot;&gt;//может быть просто объектом с несколькими полями
&amp;quot;options&amp;quot;: {
                        &amp;quot;is_checkbox&amp;quot;: false,
                        &amp;quot;is_randomize_rows&amp;quot;: true,
                        &amp;quot;is_randomize_columns&amp;quot;: false,
                        &amp;quot;sample_size&amp;quot;: -1
                    }&lt;/pre&gt;
  &lt;p id=&quot;haIE&quot;&gt;3.&lt;/p&gt;
  &lt;pre id=&quot;foIA&quot; data-lang=&quot;clike&quot;&gt;// может быть пустым объектом:
&amp;quot;options&amp;quot;: {}
&lt;/pre&gt;
  &lt;p id=&quot;Grz8&quot;&gt;4. А может и отсутствовать вовсе!&lt;/p&gt;
  &lt;p id=&quot;dDtS&quot;&gt;И вот впервые встретившись с таким... многообразием вариантов, почему-то первым на ум приходит мем:&lt;/p&gt;
  &lt;figure id=&quot;dExe&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/20/ae/20ae00c3-55b6-4936-a52a-a57ab345c9ce.png&quot; width=&quot;1187&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;FoW8&quot;&gt;Но, если отбросить эмоции, то проблему надо решать. А как правильно? Или даже по-другому: а как её решить? Как обработать четыре абсолютно разных варианта?&lt;/p&gt;
  &lt;h2 id=&quot;298Q&quot;&gt;Решение&lt;/h2&gt;
  &lt;p id=&quot;4qyq&quot;&gt;Самое простое, что может прийти в голову в таком случае — это представить тип свойства Source как object. Но проблему это не решит, так как внутри тоже могут быть варианты. &lt;/p&gt;
  &lt;p id=&quot;8kmy&quot;&gt;Можно представить его как dynamic, но проблема останется и все равно надо будет заморачиваться с парсингом входящих в него объектов, что в свою очередь принесет множество других проблем.&lt;/p&gt;
  &lt;p id=&quot;mKGa&quot;&gt;Прежде чем мы обратимся к нашему решению, пару слов о том, с чем мы имеем дело. Это — так называемые &lt;code&gt;union types&lt;/code&gt;, части объектов, которые используются в составных объектах в языках программирования, которые это позволяют. Из популярного это &lt;code&gt;JavaScript &lt;/code&gt;и &lt;code&gt;Python&lt;/code&gt;. А вот в языках со строгой типизацией, вроде &lt;code&gt;C#&lt;/code&gt;, &lt;code&gt;Java &lt;/code&gt;и других придется выкручиваться, обрабатывая все многообразие форм, которое может вернуться из такого не строгого &lt;code&gt;api&lt;/code&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;SMoY&quot;&gt;Итак, непосредственно решение.&lt;/h3&gt;
  &lt;p id=&quot;Gg10&quot;&gt;Для начала, общая структура получаемого объекта:&lt;br /&gt;&lt;/p&gt;
  &lt;pre id=&quot;W0LD&quot; data-lang=&quot;clike&quot;&gt;public class StepSource
{
    [JsonProperty(&amp;quot;id&amp;quot;)]
    public int? Id { get; set; }

    [JsonProperty(&amp;quot;block&amp;quot;)]
    public Block? Block { get; set; }
    // другие свойства
}&lt;/pre&gt;
  &lt;pre id=&quot;MHPS&quot; data-lang=&quot;clike&quot;&gt;public class Block
{
    [JsonProperty(&amp;quot;name&amp;quot;)]
    public string? Name { get; set; }
    
    [JsonProperty(&amp;quot;options&amp;quot;)]
    public BlockOptions? Options { get; set; }
    
    [JsonProperty(&amp;quot;source&amp;quot;)]
    public Source? Source { get; set; }
	
	// другие свойства
}&lt;/pre&gt;
  &lt;pre id=&quot;Fj9j&quot; data-lang=&quot;clike&quot;&gt;public class Source
 {
     [JsonProperty(&amp;quot;options&amp;quot;)]
     public OptionsWrapper? Options { get; set; }
     
     // другие свойства
 }&lt;/pre&gt;
  &lt;p id=&quot;6l4u&quot;&gt;На этом этапе немного задержимся. Как видно, и у &lt;code&gt;Block &lt;/code&gt;и у &lt;code&gt;Source &lt;/code&gt;есть свойство &lt;code&gt;Options&lt;/code&gt;, которое в некоторых деталях схоже между собой. Это порождает желание объединить их. Но так делать не надо. Потом это может сыграть злую шутку в самый неподходящий момент.&lt;/p&gt;
  &lt;p id=&quot;NZmH&quot;&gt;Продолжим. Проблема, обозначенная выше, касается свойства &lt;code&gt;Options &lt;/code&gt;у объекта &lt;code&gt;Source&lt;/code&gt;. Значит, нам нужно на этапе парсинга правильно определить тип получаемого значения и привести его к нужному объекту.&lt;/p&gt;
  &lt;p id=&quot;el59&quot;&gt;Для этой задачи определим следующий класс:&lt;/p&gt;
  &lt;pre id=&quot;xTCR&quot; data-lang=&quot;clike&quot;&gt;[JsonConverter(typeof(OptionsConverter))]
public class OptionsWrapper
{
    public List&amp;lt;OptionItem&amp;gt;? OptionsList { get; set; }
    public OptionSettings? Settings { get; set; }
    public bool IsEmpty =&amp;gt; OptionsList == null &amp;amp;&amp;amp; Settings == null;
}&lt;/pre&gt;
  &lt;p id=&quot;mfeo&quot;&gt;Здесь обратим внимание на два момента: &lt;/p&gt;
  &lt;ol id=&quot;YtAx&quot;&gt;
    &lt;li id=&quot;hQkB&quot;&gt;Мы определяем сразу возможные варианты: &lt;/li&gt;
    &lt;ol id=&quot;Uvi4&quot;&gt;
      &lt;li id=&quot;IKnm&quot;&gt;когда внутри находится список объектов&lt;/li&gt;
      &lt;li id=&quot;c3ig&quot;&gt;когда внутри только один объект (другого типа)&lt;/li&gt;
      &lt;li id=&quot;RBEA&quot;&gt;когда внутри ничего нет&lt;/li&gt;
    &lt;/ol&gt;
    &lt;li id=&quot;CybP&quot;&gt;Наличие аннотации &lt;code&gt;[JsonConverter(typeof(OptionsConverter))]&lt;/code&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;WIVd&quot;&gt;Теперь рассмотрим, что у нас получается.&lt;/p&gt;
  &lt;pre id=&quot;Ai6F&quot; data-lang=&quot;clike&quot;&gt; public class OptionItem
 {
     [JsonProperty(&amp;quot;is_correct&amp;quot;)]
     public bool IsCorrect { get; set; }
     
     [JsonProperty(&amp;quot;text&amp;quot;)]
     public string Text { get; set; }
     
     [JsonProperty(&amp;quot;feedback&amp;quot;)]
     public string Feedback { get; set; }
 }&lt;/pre&gt;
  &lt;pre id=&quot;gylJ&quot; data-lang=&quot;clike&quot;&gt; public class OptionSettings
 {
     [JsonProperty(&amp;quot;is_checkbox&amp;quot;)]
     public bool IsCheckbox { get; set; }
     
     [JsonProperty(&amp;quot;is_randomize_rows&amp;quot;)]
     public bool IsRandomizeRows { get; set; }
     
     [JsonProperty(&amp;quot;is_randomize_columns&amp;quot;)]
     public bool IsRandomizeColumns { get; set; }
     
     [JsonProperty(&amp;quot;sample_size&amp;quot;)]
     public int SampleSize { get; set; }
 }&lt;/pre&gt;
  &lt;p id=&quot;p0um&quot;&gt;И самое главное, кастомный (самописный) &lt;code&gt;JsonConverter&lt;/code&gt;:&lt;/p&gt;
  &lt;figure id=&quot;KyVG&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c0/72/c0727e7c-c732-41dc-8e19-dc90db5bcd16.png&quot; width=&quot;895&quot; /&gt;
    &lt;figcaption&gt;Картинкой оказалось нагляднее.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;rDyO&quot;&gt;Он решает несколько задач. Во-первых, теперь можно и прочитать и записать значение. То есть, можно как получить, так и отправить объект; полноценно его использовать.&lt;/p&gt;
  &lt;p id=&quot;u3Ok&quot;&gt;Во-вторых, внутри он определяет, что получено и какой тип должен получиться на выходе.&lt;/p&gt;
  &lt;p id=&quot;zbDE&quot;&gt;Пройдемся по блокам код:&lt;/p&gt;
  &lt;ol id=&quot;x6JQ&quot;&gt;
    &lt;li id=&quot;XVEy&quot;&gt;&lt;code&gt;var token = JToken.Load(reader);&lt;/code&gt; — получаем неопределенный объект&lt;/li&gt;
    &lt;li id=&quot;0Fsf&quot;&gt; Определяем, является ли полученный объект списком (коллекцией), если да, то сериализуем в список объектов &lt;code&gt;OptionItem &lt;/code&gt;и возвращаем новый объект &lt;code&gt;OptionsWrapper &lt;/code&gt;со свойством &lt;code&gt;List&amp;lt;OptionItem&amp;gt;&lt;/code&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;NsaY&quot; data-lang=&quot;clike&quot;&gt;if (token.Type == JTokenType.Array)
{
    var list = token.ToObject&amp;lt;List&amp;lt;OptionItem&amp;gt;&amp;gt;(serializer);
    return new OptionsWrapper { OptionsList = list };
}&lt;/pre&gt;
  &lt;p id=&quot;AR1m&quot;&gt;3.&lt;/p&gt;
  &lt;pre id=&quot;bDRd&quot; data-lang=&quot;clike&quot;&gt;if (token.Type == JTokenType.Object)
{
    var obj = (JObject)token;
    if (!obj.HasValues)
        return new OptionsWrapper(); 
    var props = obj.Properties()
                   .Select(p =&amp;gt; p.Name)
                   .ToList();
    if (props.Contains(&amp;quot;is_checkbox&amp;quot;) 
     || props.Contains(&amp;quot;is_randomize_rows&amp;quot;) 
     || props.Contains(&amp;quot;sample_size&amp;quot;))
    {
        var settings = obj.ToObject&amp;lt;OptionSettings&amp;gt;(serializer);
        return new OptionsWrapper { Settings = settings };
    }
}&lt;/pre&gt;
  &lt;p id=&quot;nKUs&quot;&gt;А если это объект, то продолжаем проверку. &lt;/p&gt;
  &lt;p id=&quot;b6XL&quot;&gt;Если у объекта нет значений, то возвращаем пустой объект &lt;code&gt;OptionsWrapper&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;wGSh&quot;&gt;А если у объекта есть свойства с именами (указаны внутри блока if), то создается и возвращается новый объект  &lt;code&gt;OptionsWrapper  &lt;/code&gt;с созданным свойством &lt;code&gt;OptionSettings&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;tgc5&quot;&gt;В обратную сторону это работает так же: проверяем, что в себе &amp;quot;несет&amp;quot; &lt;code&gt;OptionsWrapper &lt;/code&gt;и в зависимости от этого сериализуем объект, список или пустое объявление &lt;code&gt;json &lt;/code&gt;(&amp;#x27;&lt;code&gt;{}&lt;/code&gt;&amp;#x27;)&lt;/p&gt;
  &lt;p id=&quot;PEvU&quot;&gt;Отметим так же, что это работает с библиотекой &lt;code&gt;Newtonsoft.Json&lt;/code&gt; и не работает (не работало) с &lt;code&gt;System.Text.Json&lt;/code&gt;, увы&lt;/p&gt;
  &lt;p id=&quot;UZuJ&quot;&gt;По такому же принципу можно обрабатывать и бОльшее число вариантов от внешних сервисов. Однако, пожелаем вам пореже с такими &amp;quot;чудесами&amp;quot; сталкиваться.&lt;/p&gt;

</content></entry><entry><id>csharpmagazine:solid</id><link rel="alternate" type="text/html" href="https://teletype.in/@csharpmagazine/solid?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=csharpmagazine"></link><title>SOLID простым языком</title><published>2024-09-27T17:27:35.361Z</published><updated>2024-09-30T07:46:28.299Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/c8/f5/c8f5ed32-38f9-45eb-bb07-0af837608255.png"></media:thumbnail><category term="c" label="c#"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/41/fc/41fcdc63-ce50-4042-86db-658c21cfe029.webp&quot;&gt;В данной статье расскажем простым языком про принципы чистой архитектуры более известные как SOLID.</summary><content type="html">
  &lt;p id=&quot;lR72&quot;&gt;Термины «чистый код», и «чистая архитектура» знакомы многим. Действительно, очень известные понятия, но такие непонятные для тех, кто только погружается в мир разработки. И, если в случае с чистым кодом можно догадаться о чем речь, то с чистой архитектурой все сложнее. Впрочем, как и в разработке в целом проектирование приложения гораздо более сложная и ответственная задача, нежели просто написание кода. Не зря же следующей ступенью после старшего разработчика и тимлида зачастую вырастают в системного архитектора. И далеко не все. Значит, спроектировать всю систему, выстроить ее таким образом, чтобы она не только работала, но работала быстро и правильно, а главное могла быть расширяема и в целом поддерживаема, действительно непростое дело.&lt;/p&gt;
  &lt;p id=&quot;xQUL&quot;&gt;В то же время, понимать, как выстроить взаимосвязь между элементами в своем приложении надо каждому разработчику. Иначе результат его трудов рискует стать огромной кучей сложно поддерживаемого, не расширяемого кода.&lt;/p&gt;
  &lt;p id=&quot;XorX&quot;&gt;Одним из принципов построения правильной архитектуры приложений в ООП является принципы, а точнее принципы SOLID. SOLID – это не столько самостоятельное слово, а акроним, составленный из первых букв пяти принципов, кстати, далеко не всех, описанных Робертом Мартином. И в рамках этой статьи мы посмотрим по внимательнее на них и разберем на практике, где и как их применять. Стоит оговориться, что одной статьи для полного погружения в «чистую архитектуру» будет мало, но старт, пожалуй, вполне хороший.&lt;/p&gt;
  &lt;h2 id=&quot;A14X&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;S. Single Responsibility Principle (Принцип единственной ответственности)&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;TdjP&quot;&gt;Очень часто упоминание этого принципа сводится к следующей парадигме:&lt;/p&gt;
  &lt;p id=&quot;wFqG&quot;&gt;– каждый класс должен выполнять одну задачу&lt;/p&gt;
  &lt;p id=&quot;91OM&quot;&gt;– каждый метод должен выполнять только одну функцию,&lt;/p&gt;
  &lt;p id=&quot;B0WP&quot;&gt;И нельзя сказать, что здесь есть какие-то противоречия. Действительно, каждый метод должен выполнять одну задачу, а если он выполняет больше одной, то следует метод разбить на два и более мелких. Это улучшит читаемость, тестируемость и поддерживаемость. В случае с классами, ситуация схожая: если в рамках одного класса методы выполняют одну задачу, например CRUD-операции в репозитории, то все нормально.&lt;/p&gt;
  &lt;p id=&quot;48rF&quot;&gt;Но, есть нюанс. Сам Боб Мартин в книге «Чистая архитектура» пишет о том, что такое толкование термина «единственная ответственность» некорректно. Этот термин не столько описывает внутреннюю структуру определённого класса, сколько указывает на его предназначение для конкретного потребителя. Его цель — не просто выполнять определённую функцию, а изменяться только под нужды конкретного потребителя, для которого он предназначен.&lt;/p&gt;
  &lt;p id=&quot;wqxo&quot;&gt;Что это означает на практике? В практическом смысле все обстоит таким образом: если у нас есть некий сервис (класс), формирующий какой-то результат, то все действия с этим сервисом (добавление/изменение/удаление логики) должны производиться только для того одного «потребителя», для которого он предназначен. Пожалуй, в этом случае стоит привести пример, который раскроет тему на практике.&lt;/p&gt;
  &lt;p id=&quot;Hmgi&quot;&gt;Итак, разберем пример из, все той же, «Чистой архитектуры», слегка модернизированный: есть некий класс Employee в котором три публичных метода:&lt;/p&gt;
  &lt;ul id=&quot;0sku&quot;&gt;
    &lt;li id=&quot;EorQ&quot;&gt;&lt;code&gt;CalculatePay();&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;TLgv&quot;&gt;&lt;code&gt;ReportHours();&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;kn8r&quot;&gt;&lt;code&gt;Save();&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;QwO4&quot;&gt;Каждый из этих методов используется разными «потребителями»:&lt;/p&gt;
  &lt;ul id=&quot;MKkY&quot;&gt;
    &lt;li id=&quot;CWQ8&quot;&gt;бухгалтерия пользуется методом &lt;code&gt;CalculatePay()&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;Eily&quot;&gt;отдел кадров использует &lt;code&gt;ReportHours()&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;8GHa&quot;&gt;а методом &lt;code&gt;Save()&lt;/code&gt; пользуются администраторы баз данных.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;5U42&quot;&gt;Чем же плох такой подход? Давайте представим, что есть метод &lt;code&gt;GetRegularHours()&lt;/code&gt;, который рассчитывает рабочие часы для сотрудников и используется в этих методах. Пусть это будет приватный метод и находится в том же классе.&lt;/p&gt;
  &lt;figure id=&quot;lJvk&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c7/92/c7921681-e9e9-4369-a255-d484c91ec754.png&quot; width=&quot;349&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RcVp&quot;&gt;В какой-то момент, в бухгалтерии решили обновить систему расчета заработной платы и им стало необходимо поменять алгоритм учета рабочего времени. Разработчик, приглашенный бухгалтерией внес изменения в метод &lt;code&gt;GetRegularHours()&lt;/code&gt; и вроде бы все довольны.&lt;/p&gt;
  &lt;p id=&quot;OACR&quot;&gt;И только спустя n-времени отдел кадров обратил внимание, что показатели рабочего времени у всех сотрудников сильно изменились, что повлекло огромные убытки. Вот это и есть нарушение принципа единственной ответственности, когда класс был изменен в пользу одного потребителя, а предоставлял результаты нескольким.&lt;/p&gt;
  &lt;p id=&quot;GI72&quot;&gt;Что же нужно сделать, чтобы избежать подобных ситуаций? В первую очередь разграничить ответственность – предоставить каждому потребителю свой класс для решения своих задач, которые могут использовать одни исходные данные. Внутри этих классов будет своя логика, изменения в которой не окажут прямого влияния на других потребителей.&lt;/p&gt;
  &lt;figure id=&quot;dFyz&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/29/c7/29c75bfb-a3e4-43a2-bf4a-c05a952e99e1.jpeg&quot; width=&quot;2298&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;ijT4&quot; data-align=&quot;center&quot;&gt;O. Open-Closed Principle (Принцип открытости-закрытости)&lt;/h2&gt;
  &lt;p id=&quot;gbQX&quot;&gt;Принцип открытости-закрытости звучит в обсуждениях не так часто, как Принцип единственной ответственности, однако с точки зрения проектирования архитектуры приложения он крайне важен. Кстати, сформулирован он был в 1988 г. Бертраном Мейером и звучит он так: «Программные сущности должны быть открыты для расширения и закрыты для изменения». Что же это означает?&lt;/p&gt;
  &lt;p id=&quot;4Une&quot;&gt;Сформулируем это так: должна быть возможность расширения поведения программных сущностей без их изменения. То есть, если нужно добавить какую-то функцию, имеющийся код не должен быть подвергнут изменениям или изменения должны быть минимальны.&lt;/p&gt;
  &lt;p id=&quot;Ece0&quot;&gt;Для того чтобы добиться такого состояния приложения, нужно более гибко подходить к реализации с самого начала. Рассмотрим пример для наглядности.&lt;/p&gt;
  &lt;p id=&quot;V937&quot;&gt;Предположим, есть организация, в которой формируется финансовый отчет в виде PDF файла. В какой-то момент, в организации начали внедрять новое ПО, предоставляющее визуальное отображение различных показателей компании с графиками и метриками. Использование pdf документа в таких условиях не представляется удобным. Значит нужно сформировать данные для передачи во внедряемую систему. Текущий код, при правильной организации не будет изменен, если реализована возможность на определенном этапе получить «сырые» данные и использовать их для формирования нового представления и отправки в обновленную систему.&lt;/p&gt;
  &lt;figure id=&quot;yO8P&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/91/f9/91f97103-1dfe-452c-8afa-89b9c132a297.jpeg&quot; width=&quot;2224&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;5jKP&quot;&gt;На схеме выше изображены все уровни взаимодействия, где каждый шаг расширяет, но не изменяет текущую функциональность приложения. Фин. репозиторий забирает данные из БД и отдает их в генератор отчетов. Генератор отчетов собирает воедино все данные и передает потребителям. Раньше это был только сервис по генерации pdf документов. После добавления нового сервиса по генерации визуальных отчетов поведение других компонентов системы не изменились. Таким образом соблюдается принцип открытости-закрытости. Принцип единственной ответственности тоже соблюдается, поскольку все действия по обработке финансовых данных происходят в отдельных классах и их результат не зависит друг от друга.&lt;/p&gt;
  &lt;h2 id=&quot;iS6M&quot; data-align=&quot;center&quot;&gt;L. Liskov substitute principle (Принцип подстановки Барбары Лисков)&lt;/h2&gt;
  &lt;p id=&quot;tYfi&quot;&gt;Все в том же 1988 году Барбара Лисков написала так:&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;7Jzv&quot;&gt;«Здесь требуется что-то вроде следующего свойства подстановки: если для каждого объекта o1 типа S существует такой объект o2 типа T, что для всех программ P, определенных в терминах T, поведение P не изменяется при подстановке o1 вместо o2, то S является подтипом T.»&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;u1xp&quot;&gt;Иными словами, если у класса есть классы, его наследующие, то при замене объекта базового класса на объект класса, его наследующего не должно повлечь изменений в поведении приложения. Стоит рассмотреть схематично, как это выглядит.&lt;/p&gt;
  &lt;figure id=&quot;h1q4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c5/5c/c55c9888-6ea0-4a4e-8355-052501d37eb4.jpeg&quot; width=&quot;1358&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pD1U&quot;&gt;Приложение использует класс &lt;code&gt;Shape &lt;/code&gt;у которого есть один метод. Два класса: квадрат и прямоугольник наследуют класс &lt;code&gt;Shape &lt;/code&gt;и могут быть использованы вместо него там, где базовый класс вызывается, при этом не произойдет изменение поведения приложения.&lt;/p&gt;
  &lt;p id=&quot;LMXM&quot;&gt;Другой пример, более близкий к реальному применению. Представим интернет магазин, где можно приобрести как товар, так и услугу. С точки зрения объектной модели они отличаются не сильно, у них есть много общего – название, цена, описание. На изображении ниже приведен код этих классов:&lt;/p&gt;
  &lt;figure id=&quot;dEFe&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/09/a8/09a806f5-f9e5-4916-ae5c-89782394d28e.png&quot; width=&quot;568&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RamF&quot;&gt;Как видим, в корзину добавляются элементы &lt;code&gt;CartItem&lt;/code&gt;, в которых содержится информация об элементе и его количестве, а так же, в зависимости от количества получается цена. В корзине так же есть метод &lt;code&gt;CalculateSum()&lt;/code&gt;, который считает общую стоимость элементов в корзине. Здесь очень хорошо видно, как именно подставляются объекты разных классов (&lt;code&gt;Product&lt;/code&gt; и &lt;code&gt;Service&lt;/code&gt;) в &lt;code&gt;CartItem&lt;/code&gt;, но при этом поведение методов у &lt;code&gt;CartItem &lt;/code&gt;и &lt;code&gt;Cart &lt;/code&gt;не меняется в зависимости от содержимого. Это и есть то самое правило подстановки Барбары Лисков.&lt;/p&gt;
  &lt;h2 id=&quot;bNRY&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;I. Interface Segregation principle (Принцип разделения интерфейсов)&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;QzVS&quot;&gt;В данной части применим несколько иной подход, нежели с предыдущими принципами. Начнем с конца, а именно – с проблемы, а потом решим ее с помощью принципа разделения интерфейсов.&lt;/p&gt;
  &lt;p id=&quot;pnc5&quot;&gt;Предположим, у нас есть класс &lt;code&gt;Human&lt;/code&gt;, который реализует интерфейс &lt;code&gt;IWorker&lt;/code&gt;. И есть класс &lt;code&gt;Robot&lt;/code&gt;, который также реализует интерфейс &lt;code&gt;IWorker&lt;/code&gt;. Пока все правильно, но, взгляните на схему:&lt;/p&gt;
  &lt;figure id=&quot;Isxd&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/78/9a/789a68fb-98bd-496b-8e05-fd1e9c66069a.png&quot; width=&quot;711&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;5Qfd&quot;&gt;У интерфейса &lt;code&gt;IWorker &lt;/code&gt;есть методы, которые не используются классом &lt;code&gt;Robot&lt;/code&gt;, однако мы вынуждены их реализовывать из-за того, что реализуем интерфейс. Более того, при использовании приложения может возникнуть ситуация, когда исключение будет вызвано, поскольку вместо &lt;code&gt;Human &lt;/code&gt;метод &lt;code&gt;Eat &lt;/code&gt;был вызван у &lt;code&gt;Robot&lt;/code&gt;. Кроме того, явная реализация заведомо ненужных методов вынуждает писать много дополнительного «мертвого» кода.&lt;/p&gt;
  &lt;p id=&quot;gKA9&quot;&gt;Выходом из данной ситуации может стать разделение интерфейсов на более узконаправленные. Пример на изображении ниже:&lt;/p&gt;
  &lt;figure id=&quot;2sAq&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/ca/91/ca91347d-feb7-4afd-8654-dde07a60483b.png&quot; width=&quot;467&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;FkMz&quot;&gt;В таком виде каждый класс реализует только те интерфейсы, методы которых он будет использовать.&lt;/p&gt;
  &lt;p id=&quot;CDwf&quot;&gt;Таким образом, разделив интерфейсы, мы можем вызвать метод &lt;code&gt;ManageWork()&lt;/code&gt; и использовать в нем как &lt;code&gt;Human&lt;/code&gt;, так и &lt;code&gt;Robot&lt;/code&gt;. При этом &lt;code&gt;Robot &lt;/code&gt;реализует только один интерфейс, а &lt;code&gt;Human &lt;/code&gt;все три. Однако это никак не сказывается на работе приложения.&lt;/p&gt;
  &lt;h2 id=&quot;mwVG&quot; data-align=&quot;center&quot;&gt;D. Dependency Inversion principle (Принцип инверсии зависимостей)&lt;/h2&gt;
  &lt;p id=&quot;0auP&quot;&gt;Этот принцип говорит, что наиболее правильно для построения гибкой системы, в которых в качестве зависимостей используются абстракции, а не конкретные реализации.&lt;/p&gt;
  &lt;p id=&quot;U0YG&quot;&gt;Сразу же посмотрим, как это реализуется. Для примера рассмотрим использование DI контейнера в приложении, реализующем Телеграм бота:&lt;/p&gt;
  &lt;p id=&quot;hfq2&quot;&gt;Есть интерфейс &lt;code&gt;IUserRepository&lt;/code&gt;, в котором описаны методы стандартного CRUD-репозитория. Есть класс &lt;code&gt;UserRepository &lt;/code&gt;его реализующий. В контейнер зависимостей мы добавили интерфейс и класс его реализующий, а в классе &lt;code&gt;ClientActiveSessionUpdatesHandler &lt;/code&gt;используется в качестве зависимости &lt;code&gt;IUserRepository&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;22yI&quot;&gt;При таком подходе, вызывая метод, описанный в интерфейсе репозитория мы передаем выполнение задачи в класс, его реализующий. Если в какой-то момент нужно будет поменять реализацию методов, например использовать другую БД или на этапе разработки использовать InMemory базу данных, мы просто поменяем название класса, реализующего интерфейс &lt;code&gt;IUserRepository &lt;/code&gt;в DI-контейнере и ничего в приложении не изменится. Все компоненты продолжат работать так, как работали.&lt;/p&gt;
  &lt;p id=&quot;bGe0&quot;&gt;Таким образом, принцип инверсии зависимостей – это принцип односторонней связи между местом применения и интерфейсом, между интерфейсом и классом его реализующим. Интерфейс в этих взаимодействиях остается (и должен оставаться) максимально устойчивым к изменениям. Тогда компоненты его реализующие и использующие будут четко реализовывать и использовать методы в нем заложенные.&lt;/p&gt;
  &lt;h2 id=&quot;Zr2u&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;Заключение&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;iZw8&quot;&gt;В рамках статьи мы рассмотрели принципы, названия которых формируют устоявшийся термин SOLID. О нем много и часто говорят, спрашивают, и неспроста. Мы убедились, что соблюдение этих принципов делает приложение устойчивым к изменениям, гибким и поддерживаемым. О каждом из принципов можно еще много и долго говорить. Но это будет уже книга, а не статья. Нам же хотелось объяснить простым языком, что и для чего надо делать, чтобы архитектура ваших приложений была чистой, готовой к изменениям и модернизации, а самое главное, чтобы вы после завершения разработки могли гордо демонстрировать свой код, зная, что он высокого качества.&lt;/p&gt;

</content></entry><entry><id>csharpmagazine:srp</id><link rel="alternate" type="text/html" href="https://teletype.in/@csharpmagazine/srp?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=csharpmagazine"></link><title>Принцип единственной ответственности в ASP .NET Core MVC</title><published>2024-09-12T20:16:26.424Z</published><updated>2024-09-26T20:44:42.319Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/8a/ee/8aeed4e9-23ec-4ea5-a2a6-cab7197d8d67.png"></media:thumbnail><category term="net" label=".NET"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/51/e7/51e7de73-d7f9-411f-b884-ca8aa32f9dde.webp&quot;&gt;В этой статье поговорим про одну из важных составляющих ООП в целом и приложений на C# в частности - про принцип единой ответственности.</summary><content type="html">
  &lt;p id=&quot;Zhyl&quot;&gt;В этой статье поговорим про одну из важных составляющих ООП в целом и приложений на C# в частности - про принцип единственной ответственности.&lt;/p&gt;
  &lt;p id=&quot;j5K9&quot;&gt;Принцип единственной ответственности гласит, что каждый класс должен иметь одну четко определенную задачу. Рассмотрим на примере, где этот принцип не соблюдается:&lt;/p&gt;
  &lt;figure id=&quot;1Tre&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/98/88/98881faf-0d7e-4e86-9eb7-8ecd1481a070.png&quot; width=&quot;955&quot; /&gt;
    &lt;figcaption&gt;Пример кода, где не соблюдается принцип единой ответственности.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;MCDH&quot;&gt;Вроде в коде все нормально, а что не так? &lt;/p&gt;
  &lt;p id=&quot;jIpc&quot;&gt;А не так в нем то, что контроллер, который отвечает за взаимодействие передаваемых из фронта (интерфейса сайта) или api данных и другими компонентами приложения, в данном случае, выполняет много дополнительных, не свойственных действий. &lt;/p&gt;
  &lt;p id=&quot;yEEI&quot;&gt;То есть, контроллер - некий диспетчер. Принял данные, отдал их на обработку, получил результат обработки и отдал обратно во фронтенд или api. Он не производит вычислений, формирований строк и т.д. И контроллер не должен знать о внутреннем устройстве приложения дальше, чем на один шаг. Соответственно, он никак не может взаимодействовать напрямую с базой данных, в данном случае с контекстом.&lt;/p&gt;
  &lt;p id=&quot;Wb89&quot;&gt;Упрощенная схема взаимодействия выглядит так:&lt;/p&gt;
  &lt;p id=&quot;Sytx&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;xhzd&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/78/18/7818a747-4dad-41b8-a011-d8880a86deda.png&quot; width=&quot;872&quot; /&gt;
    &lt;figcaption&gt;Схема взаимодействия классов в приложении ASP .Net Core&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;BDUw&quot;&gt;Контроллер &amp;quot;общается&amp;quot; только с сервисами. Сервисы взаимодействуют с контроллером, другими сервисами и репозиториями. Репозитории взаимодействуют только с базой данных и сервисами их использующими. &lt;/p&gt;
  &lt;p id=&quot;9uhO&quot;&gt;Такой подход позволяет:&lt;/p&gt;
  &lt;ul id=&quot;TlxI&quot;&gt;
    &lt;li id=&quot;Cl0x&quot;&gt;четко упорядочить структуру приложения;&lt;/li&gt;
    &lt;li id=&quot;MzmU&quot;&gt;упростить тестирование;&lt;/li&gt;
    &lt;li id=&quot;6QYI&quot;&gt;определить задачи для каждого компонента приложения;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;bHYW&quot;&gt;Попробуем провести аналогию с реальным миром. Представим, что у нас есть автомобиль, в котором так же есть холодильник. И для того, чтобы приготовить еду нам нужно идти к машине. Каждый раз. А если сломается машина, мы отвезем ее в сервис. Вместе с холодильником. Не очень удобно, не так ли? В данном примере автомобиль выполняет две функции - свою непосредственную по перемещению владельца из точки А в точку Б и вторую, несвойственную ему функцию хранения продуктов. &lt;/p&gt;
  &lt;p id=&quot;HNT6&quot;&gt;Именно для того, чтобы пользоваться каждой функцией полноценно и не зависеть от других компонентов, нужно разделять их ответственность (читай функционал).&lt;/p&gt;
  &lt;p id=&quot;0Tyy&quot;&gt;Теперь рассмотрим первоначальный пример, но уже с разделением ответственности:&lt;/p&gt;
  &lt;p id=&quot;tZYu&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;mISt&quot; data-lang=&quot;clike&quot;&gt;public class UserController : Controller
{
    private readonly IUserService _userService;

    public UserController(IUserService userService)
    {
        _userService = userService;
    }

    public IActionResult Index()
    {
        var users = _userService.GetUsers();
        return View(users);
    }

    public IActionResult Create(string fullName, int age, string email)
    {
        if (!ModelState.IsValid)
        {
            return View();
        }

        _userService.CreateUser(fullName, age, email);
        return RedirectToAction(&amp;quot;Index&amp;quot;);
    }

    public IActionResult Delete(int id)
    {
        _userService.DeleteUser(id);
        return RedirectToAction(&amp;quot;Index&amp;quot;);
    }
}

public interface IUserService
{
    IEnumerable&amp;lt;User&amp;gt; GetAll();
    void Create(string fullName, int age);
    void Delete(int id);
}

public class UserService : IUserService
{
    private readonly UserRepository _repository;
    private readonly EmailService _emailService;

    public UserService(UserRepository repository, EmailService emailService)
    {
        _repository = repository;
        _emailService = emailService;
    }

    public IEnumerable&amp;lt;User&amp;gt; GetAll()
    {
        return _repository.GetAll();
    }

    public void Create(string fullName, int age, string email)
    {
        var user = new User(fullName, age, email)
        
        _repository.Save(user);

        _emailService.SendWelcomeEmail(email);
    }

    public void Delete(int id)
    {
        var email = _repository.Get(id).Email;
        
        _repository.Delete(id);

        _emailService.SendGoodByeEmail(email);
    }
}

public class UserRepository
{
    private readonly DbContext _dbContext;

    public UserRepository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IEnumerable&amp;lt;User&amp;gt; GetAll()
    {
        return _dbContext.Users.ToList();
    }

    public User Get(int id)
    {
        return _dbContext.Users.Where(u =&amp;gt; u.id == id).First();
    }

    public void Save(User user)
    {
        _dbContext.Users.Add(user);
        _dbContext.SaveChanges();
    }

    public void Delete(int id)
    {
        var user = _dbContext.Users.Find(id);
        if (user != null)
        {
            _dbContext.Users.Remove(user);
            _dbContext.SaveChanges();
        }
    }
}

public class EmailService
{
    public void SendWelcomeEmail(string email)
    {
        // код отправки электронной почты
    }

    public void SendGoodByeEmail(string email)
    {
        // код отправки электронной почты
    }
}&lt;/pre&gt;
  &lt;p id=&quot;QVdy&quot;&gt;Теперь в репозитории происходит только выполнение CRUD операций, в сервисе вызываются методы репозитория и сервиса отправки электронной почты, и отдаются данные в контроллер , а контроллер занимается передачей запросов и возвратом результата в пользовательский интерфейс.&lt;/p&gt;
  &lt;p id=&quot;4BoO&quot;&gt;В таком подходе может быть больший по объему код, но он лучше тестируется, каждый компонент выполняет свою роль, а код в целом менее связан. &lt;/p&gt;
  &lt;p id=&quot;QQb1&quot;&gt;Таким образом, соблюдения принципа единственной ответственности делает код более читаемым, легко поддерживаемым, обновляемым и тестируемым. Пишите чистый код, до встречи!&lt;/p&gt;

</content></entry><entry><id>csharpmagazine:winappframeworkcomparison</id><link rel="alternate" type="text/html" href="https://teletype.in/@csharpmagazine/winappframeworkcomparison?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=csharpmagazine"></link><title>Сравнение технологий для написания оконных приложений.</title><published>2024-08-13T20:46:51.858Z</published><updated>2024-08-18T15:34:40.735Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/da/8a/da8a74bd-e250-4f82-bb46-a0586eec92f2.png"></media:thumbnail><category term="net" label=".NET"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/bc/3b/bc3b6ada-ad79-4abb-8188-aff47bda217b.png&quot;&gt;Как-то раз в одном чате прозвучала идея сравнить разные технологии для написания оконных приложений. В частности, под Windows. Используя средства языка C#. То есть, конечно, можно это сделать и с помощью C++, Python, Rust, JS и других языков, но мы же шарписты, нам интереснее именно то, что мы можем сами использовать, не меняя язык программирования.</summary><content type="html">
  &lt;figure id=&quot;Z0ha&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bc/3b/bc3b6ada-ad79-4abb-8188-aff47bda217b.png&quot; width=&quot;479&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Cwc8&quot;&gt;Как-то раз в одном чате прозвучала идея сравнить разные технологии для написания оконных приложений. В частности, под Windows. Используя средства языка C#. То есть, конечно, можно это сделать и с помощью C++, Python, Rust, JS и других языков, но мы же шарписты, нам интереснее именно то, что мы можем сами использовать, не меняя язык программирования.&lt;/p&gt;
  &lt;p id=&quot;Wt2Q&quot;&gt;Итак, оконные приложения на C#. Их можно написать, используя:&lt;/p&gt;
  &lt;ul id=&quot;qvui&quot;&gt;
    &lt;li id=&quot;9qcg&quot;&gt;Windows Forms&lt;/li&gt;
    &lt;li id=&quot;hhSq&quot;&gt;WPF&lt;/li&gt;
    &lt;li id=&quot;Nr5h&quot;&gt;WinUI&lt;/li&gt;
    &lt;li id=&quot;wXVW&quot;&gt;.NET MAUI&lt;/li&gt;
    &lt;li id=&quot;nB96&quot;&gt;Avalonia&lt;/li&gt;
    &lt;li id=&quot;Wmss&quot;&gt;Uno Platform&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;scV1&quot;&gt;Такое многообразие фреймворков обусловлено тем, что язык развивался, менялись подходы, совершенствовались технологии. А в какой-то момент к разработке подключились сторонние разработчики и к средствам Microsoft добавились Avalonia и Uno.&lt;/p&gt;
  &lt;p id=&quot;nfb4&quot;&gt;Таким образом, для того чтобы начать писать оконные приложения надо решить, какую технологию использовать. Но, кроме того, неплохо бы понимать, где будет приложение использоваться – только на Windows или есть необходимость в запуске на MacOS и Linux. А может быть еще и на IOS и Android?&lt;/p&gt;
  &lt;p id=&quot;iNFU&quot;&gt;В общем, в этой статье попробуем разобраться в возможностях этих фреймворков и сравним их. Сравнивать будем по таким критериям:&lt;/p&gt;
  &lt;ul id=&quot;B18l&quot;&gt;
    &lt;li id=&quot;oQJz&quot;&gt;Платформы, в которых можно запустить приложение&lt;/li&gt;
    &lt;li id=&quot;ZBU4&quot;&gt;Сложность разработки&lt;/li&gt;
    &lt;li id=&quot;zBzc&quot;&gt;Потребляемые ресурсы&lt;/li&gt;
    &lt;li id=&quot;4ddz&quot;&gt;Скорость запуска&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Cs0d&quot;&gt;Для корректности сравнения будем использовать одну логику для всех приложений. Более того, попытаемся сделать их максимально похожими друг на друга.&lt;/p&gt;
  &lt;h2 id=&quot;MoMt&quot;&gt;Часть 1. Логика&lt;/h2&gt;
  &lt;p id=&quot;F3z2&quot;&gt;Итак, чтобы не сравнивать пустые приложения без какой-либо функциональности, и при этом не перегружать излишним наполнением,   реализовал простейшую версию игры Крестики-Нолики. Логика самой игры простая: есть два игрока Х и О. Начинает игрок Х потом ходит О. Кто первым соберет три своих символа в ряд — выигрывает, если после последнего хода победителя нет, то ничья. Игра осуществляется с одного устройства, по очереди.&lt;/p&gt;
  &lt;p id=&quot;1eyO&quot;&gt;Весь код логики практически один в один переходит из приложения в приложение, за исключением некоторых особенностей в разных фреймворках. Например, в Авалонии нет встроенного механизма отображения всплывающих окон (Message Box) и для того, чтобы он появился надо установить дополнительный пакет. Не сложно, но нюанс. В некоторых фреймворках нельзя напрямую посчитать кнопки из интерфейса и надо придумывать обходные пути.&lt;/p&gt;
  &lt;p id=&quot;ry71&quot;&gt;Важно также отметить, что везде, кроме WinForms по-хорошему следует использовать паттерн MVVM, однако для простейшего приложения заморачиваться не стал и работает все напрямую.&lt;/p&gt;
  &lt;h2 id=&quot;RdHX&quot;&gt;Часть 2. Пользовательский интерфейс&lt;/h2&gt;
  &lt;p id=&quot;JZ3W&quot;&gt;Разработка UI в приложениях C# отличается не очень сильно друг от друга. Выделяется разве что WinForms, где элементы интерфейса размещаются обычно вручную в графическом редакторе, что в свою очередь весьма дружелюбно по отношению к людям без опыта.&lt;/p&gt;
  &lt;p id=&quot;eB9C&quot;&gt;Еще можно размещать элементы вручную в WPF, однако рекомендуется все же выстраивать интерфейс вручную задавая параметры в коде страницы.&lt;/p&gt;
  &lt;p id=&quot;pwnh&quot;&gt;Осложняет процесс разработки внешнего вида отсутствие интерактивного отображения получаемого результата в MAUI и WinUI – там посмотреть на результат можно только после сборки и запуска. Да, есть такая функция как Hot Reload, когда изменения будут появляться после горячей перезагрузки без перезапуска всего приложения, но тоже не без нюансов. Во-первых, при горячей перезагрузке теряется состояние приложения (если оно отдельно не сохраняется где-то), например при игре уже есть несколько крестиков и ноликов, то после Hot Reload они «забываются». Во-вторых, бывает, что изменения не отображаются и все равно приходится перезапускать приложение. Не баг, а фича, как говорится.&lt;/p&gt;
  &lt;p id=&quot;PChT&quot;&gt;В целом же привыкнуть можно ко всему и даже потом возвращаться к WinForms становится тяжко – хочется все настраивать самому.&lt;/p&gt;
  &lt;p id=&quot;bn1B&quot;&gt;В конечном итоге интерфейс готовых приложений получился таким:&lt;/p&gt;
  &lt;p id=&quot;hSH7&quot;&gt;Windows Forms &lt;/p&gt;
  &lt;figure id=&quot;LAQo&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f3/ad/f3add866-4d27-4480-a4a6-ea599ff9524a.png&quot; width=&quot;512&quot; /&gt;
    &lt;figcaption&gt;WinForms&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;kPIc&quot;&gt;WPF&lt;/p&gt;
  &lt;figure id=&quot;Kk8n&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXezYeU7rTqsA1ZEXus02_AGyaMcQU6thhs33EcUpil4dLHk5M4VpkRWlh3vxPvggVair5Bc0Bp_juSAz8nkqCNd7hyf3yX5-vlXpEQehhur_Yio3eDWf5Z8VJWQLXepn0ugryIgWlLWxVjQrw13Y_Jd0VZdnnNGOjfzBihKDQ?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;524&quot; /&gt;
    &lt;figcaption&gt;WPF&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;FAYf&quot;&gt;WinUI&lt;/p&gt;
  &lt;figure id=&quot;HT0F&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXcLwcbBurFvMfJr-h0FE9mpZyBeRUQM9uNX1HC69cq1si22PMTQlkwQs7eKiNEpiy4BUFgFtKVvrk_Iv8oWrQMnXK3tbxwcgcP-0zRV_lT2Okz1udBVM-2D9dHLgkeehgMhJD5MXF160MK6TNosJ1p_vmQAg-JvVLphV9li?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;521&quot; /&gt;
    &lt;figcaption&gt;WinUI&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;P5ou&quot;&gt;MAUI &lt;/p&gt;
  &lt;figure id=&quot;W917&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/57/71/5771ce5d-a726-49da-9b56-dd5f19d11f82.png&quot; width=&quot;523&quot; /&gt;
    &lt;figcaption&gt;MAUI&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;O1Bv&quot;&gt;Avalonia&lt;/p&gt;
  &lt;figure id=&quot;Br1D&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e3/46/e3463d51-fbf9-47c1-ac3d-7ff31102bd5c.png&quot; width=&quot;519&quot; /&gt;
    &lt;figcaption&gt;Avalonia&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;1FuF&quot;&gt;Uno Platform&lt;/p&gt;
  &lt;figure id=&quot;KJaJ&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c3/3c/c33cceb7-b6ca-480e-84e5-a95f930e606d.png&quot; width=&quot;517&quot; /&gt;
    &lt;figcaption&gt;Uno Platform&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;yf7C&quot;&gt;Я не сильно старался сделать их идентичными, потому что и задачи такой не было, да и на последующие измерения цвет кнопочек не повлияет. Более того, можно обратить внимание, что некоторые между собой похожи. Это результат применения в них компонентов WinUI. В MAUI дополнительно используются фирменные шрифты и Material Design – поэтому кнопки со скругленными углами. А еще WinUI подтягивает основную тему из системы и поэтому все, кроме WinForms и WPF темные. Соответственно в первых двух нужно отдельно добавлять обработку светлой/темной темы.&lt;/p&gt;
  &lt;h2 id=&quot;qCJj&quot;&gt;Часть 3. Платформы&lt;/h2&gt;
  &lt;p id=&quot;lyY4&quot;&gt;Тут мы подходим к глобальным возможностям всех фреймворков. Итак, таблица ниже показывает, под какие платформы может быть собрано приложение.&lt;/p&gt;
  &lt;figure id=&quot;kzGd&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/27/d0/27d025e0-2b47-406f-9622-c8c9fa8bbb2e.png&quot; width=&quot;704&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bYMC&quot;&gt;Как видим, что вполне логично, все шесть фреймворков позволяют писать оконные приложения для Windows. Но есть и различия. Они кроются как в кроссплатформенности MAUI, Avalonia и Uno, так и в компонентах, которые используются для отрисовки графического интерфейса.&lt;/p&gt;
  &lt;p id=&quot;Lizi&quot;&gt;Углубляться сильно не будем в дебри, но стоит знать, что MAUI Авалония используют компоненты WinUI, Uno в свою очередь может реализовывать оконное приложение как в WPF варианте, так и WinUI. Это может быть полезно в отдельных случаях.&lt;/p&gt;
  &lt;p id=&quot;fCnd&quot;&gt;Кроме того, нативные фреймворки WinForms, WPF и WinUI отличаются технически:&lt;/p&gt;
  &lt;p id=&quot;OCk9&quot;&gt;- Windows Forms использует GDI+ для отрисовки интерфейса&lt;/p&gt;
  &lt;p id=&quot;Gd7U&quot;&gt;- WPF использует DirectX для рендеринга&lt;/p&gt;
  &lt;p id=&quot;EsXg&quot;&gt;- WinUI использует для рендеринга DirectX/DirectComposition&lt;/p&gt;
  &lt;p id=&quot;OevB&quot;&gt;Так же, есть различие в используемых компонентах, библиотеках и подходах. Иными словами, все технологии, представленные в статье, имеют множество различий при схожей функциональности даже в рамках одной целевой платформы.&lt;/p&gt;
  &lt;h2 id=&quot;9BGx&quot;&gt;Часть 4. Ресурсы и скорость запуска&lt;/h2&gt;
  &lt;p id=&quot;2sNu&quot;&gt;Сейчас мы подошли к той части «исследования», когда в дело вступают цифры. В данной части посмотрим на то, сколько приложения занимают места в оперативной памяти. Сравнивать будем в двух режимах: в Visual Studio в режиме Debug и в диспетчере задач запущенное опубликованное приложение (конфигурация Release).&lt;/p&gt;
  &lt;figure id=&quot;KFVx&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/15/80/1580cf4a-30d1-4d1d-ab7a-1c22c985d2cb.png&quot; width=&quot;698&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fbHt&quot;&gt;Эти значения получены из Visual Studio при запуске отладки каждого приложения по отдельности.&lt;/p&gt;
  &lt;p id=&quot;tfXs&quot;&gt;А вот релизную сборку я решил проверить более тщательно. Кроме того, стояла задача проверить скорость запуска каждого  приложения. Понятно, что все они с такой мизерной функциональностью не должны загружаться долго, однако различия в скорости есть.&lt;/p&gt;
  &lt;p id=&quot;e9MS&quot;&gt;Для сбора данных я написал скрипт для PowerShell, который запускает приложение, ждет, когда оно загрузится (станет отзывчивым), дополнительно ждет пару секунд на случай неполной загрузки и выключает приложение. И так 100 раз подряд для каждого приложения. Попутно скрипт собирает данные об используемом объеме памяти и вычисляет скорость запуска.&lt;/p&gt;
  &lt;p id=&quot;XOj3&quot;&gt;В процессе тестов, обратил внимание, что некоторые приложения занимают значительный объем в памяти. Поискал информацию, с чем это может быть связано и нашел вариант, где ответственность возлагалась на запаздывающую сборку мусора. Сомнительно, но ок — добавил в скрипт принудительный вызов GC.&lt;/p&gt;
  &lt;p id=&quot;niFA&quot;&gt;Код скрипта:&lt;/p&gt;
  &lt;p id=&quot;u7Sg&quot;&gt;&lt;code&gt;[Console]::OutputEncoding = [System.Text.Encoding]::UTF8&lt;br /&gt;$apps = @(&lt;br /&gt;&amp;quot;PATH_TO_TicTacToeUno.exe&amp;quot;,&lt;br /&gt;&amp;quot;PATH_TO_TicTacToeMAUIApp.exe&amp;quot;,&lt;br /&gt;&amp;quot;PATH_TO_TicTacToeWPF.exe&amp;quot;,&lt;br /&gt;&amp;quot;PATH_TO_TicTacToeAvalonia.Desktop.exe&amp;quot;,&lt;br /&gt;&amp;quot;PATH_TO_TicTacToeWinUI.exe&amp;quot;,&lt;br /&gt;&amp;quot;PATH_TO_TicTacToeWinForms.exe&amp;quot;&lt;br /&gt;)&lt;br /&gt;$results = @{}&lt;br /&gt;foreach ($app in $apps) {&lt;br /&gt;$appName = [System.IO.Path]::GetFileNameWithoutExtension($app)&lt;br /&gt;$times = @()&lt;br /&gt;$memoryUsages = @()&lt;br /&gt;for ($i = 1; $i -le 100; $i++) {&lt;br /&gt;Write-Host &amp;quot;Starting $appName (Iteration $i of 100)...&amp;quot;&lt;br /&gt;$start = Get-Date&lt;br /&gt;$process = Start-Process $app -PassThru&lt;br /&gt;# Ждем, пока главное окно приложения не станет отзывчивым&lt;br /&gt;while (-not $process.MainWindowHandle) {&lt;br /&gt;Start-Sleep -Milliseconds 100&lt;br /&gt;}&lt;br /&gt;$end = Get-Date&lt;br /&gt;$duration = ($end - $start).TotalSeconds&lt;br /&gt;$times += $duration&lt;br /&gt;# Измеряем использование памяти&lt;br /&gt;Start-Sleep -Seconds 2  # Даем приложению время полностью загрузиться&lt;br /&gt;$memory = (Get-Process -Id $process.Id).PrivateMemorySize64 / 1MB&lt;br /&gt;$memoryUsages += $memory&lt;br /&gt;# Закрываем приложение&lt;br /&gt;Stop-Process $process.Id -Force&lt;br /&gt;while (-not $process.HasExited) {&lt;br /&gt;Start-Sleep -Milliseconds 100&lt;br /&gt;}&lt;br /&gt;# Освобождение ресурсов и сборка мусора&lt;br /&gt;$process.Dispose()&lt;br /&gt;[System.GC]::Collect()&lt;br /&gt;[System.GC]::WaitForPendingFinalizers()&lt;br /&gt;# Ждем немного перед следующим запуском&lt;br /&gt;Start-Sleep -Seconds 5&lt;br /&gt;}&lt;br /&gt;$averageTime = ($times | Measure-Object -Average).Average&lt;br /&gt;$averageMemory = ($memoryUsages | Measure-Object -Average).Average&lt;br /&gt;$results[$appName] = @{&lt;br /&gt;Time = $averageTime&lt;br /&gt;Memory = $averageMemory&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;# Вывод результатов&lt;br /&gt;Write-Host &amp;quot;&amp;#x60;nResults after 100 launches:&amp;quot;&lt;br /&gt;$results.GetEnumerator() | Sort-Object {$_.Value.Time} | ForEach-Object {&lt;br /&gt;Write-Host (&amp;quot;{0,-30} : StartUp Time: {1:N3} seconds, Memory Usage: {2:N2} Mb&amp;quot; -f $_.Key, $_.Value.Time, $_.Value.Memory)&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;rpQk&quot;&gt;Дополнительно отмечу, что скрипт я запускал по несколько раз с некоторыми изменениями, в частности брал данные о памяти как из process.WorkingSet64 так и process.PrivateMemorySize64, а так же для подстраховки продублировал тесты в Python-скрипте. Суммарно вышло что-то около 500-600 запусков каждого приложения, что в общем дает вполне понятную картину.&lt;/p&gt;
  &lt;p id=&quot;yQzk&quot;&gt;Еще, прежде чем показать результаты, оговорюсь относительно WinUI приложения — оно доставило больше всех хлопот, как ни странно,   поскольку запускалось уверенно в VS и даже тесты проходили в уже готовом и установленном приложении, однако по факту не запускалось. Ошибка с Windows.ui.xaml.dll вызываемая непонятно по какой причине, не давала мне покоя несколько дней. Несколько тестов были проведены с этой “особенностью”. Тем не менее глобально, после того, как проблема была решена, ничего в цифрах не поменялось кардинально.&lt;/p&gt;
  &lt;p id=&quot;Sr4M&quot;&gt;Итак, ниже скриншоты из PowerShell:&lt;/p&gt;
  &lt;p id=&quot;26BI&quot;&gt;1 запуск:&lt;/p&gt;
  &lt;figure id=&quot;y3WM&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXfiJyXwZgrNCe0agPIlyoyIPR51bt3VknzXBNkR8o7unnC5bYf11vQmQK7BGwNSLiT9FtRckIT3l2PWbVg3xR_xNZdDA3wI2Z9jwKZ1kMlVwK96OETGIAKImj4RfGtJ6KHfjOsSqAz5ZO5T9iaooRGNObXB?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;658&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VHex&quot;&gt;5 запусков&lt;/p&gt;
  &lt;figure id=&quot;Uaxu&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXeGnl0SFZ_ZO9ZUvumMnaT_VKk1yBpCymtqj5WMLBaN73OAN7rU-n2GLwbSR2-uOLk8l9Anjc6L_iwSDuMF8-kI3j_-wJ-ygldjdZBUJbakyQxqttpeO0--ZG8D9dbRw0hR_F8HyssApwgiTJBi4DW424lL?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;658&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;10HR&quot;&gt;10 запусков:&lt;/p&gt;
  &lt;figure id=&quot;5rSo&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXei1vUKrIoAcDVyBjjk7_Q215bKfCTCzIoGIZxyzixsG1yDzc6yheXWD0xq42SRjrOe5jYzkgvYUJ3KjHfOFu2fC55QeVf3VlJ74iVB4pvV_SY1JurcA4tY5hM4KOzdr8fTCUdtKDRADx0b2nRN8zgVH75X?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;658&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;UXkr&quot;&gt;100 запусков:&lt;/p&gt;
  &lt;figure id=&quot;afV8&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXfPQoBhb0bCepbOuiP0lo5uIOoYCYVNbz5fdD1Y0K_Xk97Jj_4QP2zJe1qkUThNPFcTHvHz74moU-3c_EEnWv94kZlhYa2YwbWjX7ZwnW-Azji0yV-Twl7mPbzI8XOKlpAFONY8KMIY-bv3n8Aq0oVy4nw?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;658&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PtM6&quot;&gt;10 запусков на Python:&lt;/p&gt;
  &lt;figure id=&quot;AMHs&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXfOBXubwbaklyT4EBkUwLvAtJRZp0p83q-1UopYt5UF5z1A0d6WBKssl8GozOnT1iEcVRHmU2lRts_td3MqcnkkpTFfn-zVRlX44rZlz-vyPau0x8rvbWAvMXZ6dnt9foeUjmMktQFYfymZZjZRA3gXNcey?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;658&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;6kNE&quot;&gt;Результаты интересные. По скорости запуска различия есть, но по сути на уровне погрешности — все запускаются быстро. Причем, когда наблюдаешь вживую за процессом выполнения скрипта, заметно, что первые 3-5 раз загрузка UI происходит дольше, чем последующие. Связано это с кэшированием на уровне ОС. Правда актуально это для ClickOnce-сборок в большей степени, но заметно и на WInUI, которую запускал без установки.&lt;/p&gt;
  &lt;p id=&quot;OmBf&quot;&gt;Гораздо интереснее ситуация с памятью. Тут и явный лидер — неожиданно - Uno Platform, и явный аутсайдер - WPF.&lt;/p&gt;
  &lt;p id=&quot;OsFO&quot;&gt;Самый последний тест, при котором все уже работало и не падало - 100 итераций в PowerShell. Там же и минимальное значение задействованных ресурсов - в среднем 1,46 Мб. Тоже, скорее всего, связано с каким-то кэшированием, потому что в других тестах стабильно требовал ~ 8Мб.&lt;/p&gt;
  &lt;p id=&quot;3NZ9&quot;&gt;Но всегда, во всех тестах, больше всего памяти отъедал WPF. Скорее всего это связано с тем, что учитываются “общие ресурсы” используемые другими приложениями в это же время в системе. По-крайней мере именно из-за этого вносил изменение в скрипт для получения данных из &lt;code&gt;PrivateMemorySize64&lt;/code&gt;, однако тесты показали, что разницы нет. Возможна ли тут ошибка? Да, вполне может быть. Но и Python-скрипт показал такие же данные, поэтому я склоняюсь все же к тому, что WPF действительно самый ресурсоемкий фреймворк.&lt;/p&gt;
  &lt;p id=&quot;wRM4&quot;&gt;Но я предполагал, что им окажется MAUI. И по используемым ресурсам, MAUI лишь WPF уступил в объеме задействованной памяти. В это же время, скорость запуска во всех тестах у MAUI была самой низкой.&lt;/p&gt;
  &lt;h2 id=&quot;6jwi&quot;&gt;Часть 5. Сложность разработки&lt;/h2&gt;
  &lt;p id=&quot;luCB&quot;&gt;На самом деле, оценить сложность разработки в каждом конкретном фреймворке нельзя. Когда он, фреймворк, для тебя новый — сложно. Со временем становится сильно проще. Впрочем, как и в любом другом деле. Поэтому сказать однозначно, что в MAUI сложнее разрабатывать десктопное приложение, чем в WinUI, например, нельзя.&lt;/p&gt;
  &lt;p id=&quot;N1gl&quot;&gt;Но совершенно точно WinForms - самый простой из представленных фреймворков в освоении. Тому есть два объяснения — графический редактор (дизайнер) и отсутствие необходимости в использовании MVVM паттерна и бесконечного числа биндингов.&lt;/p&gt;
  &lt;p id=&quot;M30o&quot;&gt;Но, опять же, все зависит от сложности проекта. В моем тестовом проекте везде было одинаково несложно. Даже с учетом того, что опыта с Uno и WinUI до последнего времени у меня не было.&lt;/p&gt;
  &lt;h2 id=&quot;3c9U&quot;&gt;Часть 6. Сборка и развертывание&lt;/h2&gt;
  &lt;p id=&quot;gj1a&quot;&gt;Пару слов про то, насколько сложно подготовить проект к публикации и использовать его потом.&lt;/p&gt;
  &lt;p id=&quot;wXzN&quot;&gt;В целом, процесс должен быть не очень сложным везде. На практике, есть множество нюансов. В частности, неожиданно WinUI преподнес неприятные сюрпризы. Различных решений проблемы я нашел множество, но ни одно так и не помогло и, в конечном счете, запускал приложение для тестов из папки Release. Остальные приложения собрались и установились без проблем.&lt;/p&gt;
  &lt;p id=&quot;smdb&quot;&gt;Дополнительно надо иметь в виду, что приложению требуется сертификат. То есть да, можно не подписывать приложение вовсе, но и распространять его будет сложнее - Windows на сторонних компьютерах будет ругаться при установке. Поэтому, хотя бы тестовым сертификатом подписать приложение стоит. А вот полноценный сертификат стоит денег, а в текущих условиях еще и надо найти, где его приобрести.&lt;/p&gt;
  &lt;p id=&quot;fAZq&quot;&gt;Конечно, это всё нюансы и наверно есть какие-то пути решения вопроса, да и вопрос сам не первоочередной важности. Но знать об этом стоит.&lt;/p&gt;
  &lt;h2 id=&quot;49Z6&quot;&gt;Часть 7. Итог&lt;/h2&gt;
  &lt;p id=&quot;qNi7&quot;&gt;Что ж, стоит подвести итоги нашего небольшого исследования-сравнения. Для кого вообще оно делалось? Для тех, кто не занимался разработкой оконных приложений, но хотел бы понять, какие есть актуальные технологии. И вот эти технологии мы рассмотрели. Весьма поверхностно, конечно, но и задачи писать энциклопедию о том, как написать оконное приложение со всеми тонкостями, не стояло. А вот сделать вывод о том, с чего начать вкатываться в мир разработки приложений под Windows, а потом и других платформ, вполне можно.&lt;/p&gt;
  &lt;p id=&quot;oS52&quot;&gt;Мое видение, основанное на имевшемся ранее и полученном сейчас такое:&lt;/p&gt;
  &lt;p id=&quot;tAFN&quot;&gt;- Начинать стоит все-таки с базы – с Windows Forms. Да, технология не нова, даже можно ее считать устаревшей. Но она живая и функциональная. Многие приемы на ней можно отработать, да и собрать действительно сложно приложение с графическим интерфейсом вполне реально. Да чего там – так делали, делали много и долго. &lt;/p&gt;
  &lt;p id=&quot;RNY7&quot;&gt;- Вторым шагом стоит изучить WPF. Эта технология позволит развить понимание паттерна MVVM, проектирование интерфейса в XAML и откроет путь к более современным стекам.&lt;/p&gt;
  &lt;p id=&quot;9YNK&quot;&gt;- Дальше путь открыт ко всему. Я был приятно удивлен возможностям Uno Platform, не ожидал. Кроме того, что можно написать приложение под любую актуальную ОС, в том числе и мобильную, так еще и весьма бережно эта технология относится, как оказалось, к ресурсам. Не то, чтобы это сильно критично, но, когда проект большой и нужно бороться за производительность, приложение, потребляющее меньше ресурсов будет выглядеть предпочтительнее на мой взгляд.&lt;/p&gt;
  &lt;p id=&quot;jH1k&quot;&gt;Еще из важного – возможность собрать дизайн приложения Uno в Figma и импортировать код интерфейса прямо в проект в виде XAML-кода! Такого нет ни у кого из других рассмотренных фреймворков.&lt;/p&gt;
  &lt;p id=&quot;ZbHa&quot;&gt;К сожалению, не для всех технологий есть качественные и полноценные обучающие материалы, особенно на русском языке. Тем не менее, тут есть что порекомендовать:&lt;/p&gt;
  &lt;ul id=&quot;M3VB&quot;&gt;
    &lt;li id=&quot;jvY2&quot;&gt;Изучить Windows Forms с углубленным пониманием ООП можно по &lt;a href=&quot;https://stepik.org/a/58658&quot; target=&quot;_blank&quot;&gt;ссылке&lt;/a&gt;;&lt;/li&gt;
    &lt;li id=&quot;fUCH&quot;&gt;Изучить WPF можно по этой &lt;a href=&quot;https://stepik.org/a/184703&quot; target=&quot;_blank&quot;&gt;ссылке&lt;/a&gt;;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;QcAu&quot;&gt;И, напоследок, пару слов о размере приложения на диске.&lt;/p&gt;
  &lt;figure id=&quot;9hC5&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXdAtNgJDeV-GuSs0-KWvQRpxa2IXIISreuJpsDLvB75x_hpJPonXAuIS3SKjnReMetnJb8HvCVXUID0bcoT6Sjvo_pUmR_at2W5TMNJHkQvx616XiK19ZCx1T-jVxbUkWLNSN6LSiTHT6HYaXwMNnlL7dTG?key=BBjfDNbgTBO6UKZAFTI4nQ&quot; width=&quot;658&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;rRiE&quot;&gt;Здесь в папках располагаются установщики. WinForms - ожидаемо, самое “легкое” приложение. MAUI - без комментариев. Хотя нет, уточню, что в сборке MAUI отсутствуют билды под другие платформы, то есть это именно Windows-приложение.&lt;/p&gt;
  &lt;p id=&quot;tnJc&quot;&gt;На этом все. Делайте свои выводы, пробуйте разрабатывать оконные приложения и успехов!&lt;/p&gt;
  &lt;p id=&quot;qpFL&quot;&gt;P.S. исходный код всех проектов доступен по ссылке: &lt;a href=&quot;https://github.com/algmironov/WinAppFrameworksComparison&quot; target=&quot;_blank&quot;&gt;https://github.com/algmironov/WinAppFrameworksComparison&lt;/a&gt;&lt;/p&gt;

</content></entry></feed>