<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksandr Martyniuk</title>
    <description>The latest articles on DEV Community by Oleksandr Martyniuk (@olesmartyniuk).</description>
    <link>https://dev.to/olesmartyniuk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F267233%2F24376895-4d3f-4614-b4bb-c82d9be99cf0.jpg</url>
      <title>DEV Community: Oleksandr Martyniuk</title>
      <link>https://dev.to/olesmartyniuk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/olesmartyniuk"/>
    <language>en</language>
    <item>
      <title>7 проблем в автотестах</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Mon, 01 Mar 2021 12:19:45 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/7-lo</link>
      <guid>https://dev.to/olesmartyniuk/7-lo</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l83f2Mf4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m04l54mp8rz0rd44jd90.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l83f2Mf4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m04l54mp8rz0rd44jd90.jpg" alt="Обкладинка"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;В своїй практиці я зустрічав проєкти з різними підходами до тестування: деякі мали 99% покриттям юніт-тестами, а в інших автоматичне тестування було відсутнє взагалі. В цій статті я хочу звернути увагу на характерні проблеми з автоматичними тестами, що мені зустрічались, і як їх вирішити. &lt;/p&gt;

&lt;p&gt;Кожен хороший програміст переймається якістю власного коду, тому писати тести - це частина нашої роботи. Далі мова піде саме про тести, які створюються програмістами під час написання коду і проблеми в цих тестах. Це стосується як системних тестів, так і інтеграційних та модульних.&lt;/p&gt;

&lt;h1&gt;
  
  
  Проблема №1. Тести залежать від випадкових даних.
&lt;/h1&gt;

&lt;p&gt;Важливою властивістю тестів є їх контрольованість. Тести повинні повертати однаковий результат в незалежності від пори року та температури в приміщенні. Якщо ваші тести випадково зазнають невдачі, ви не можете на них покладатись. Є великий ризик, що такі тести будуть тимчасово відключені або зовсім видалені.&lt;/p&gt;

&lt;p&gt;Я пам'ятаю випадок, коли деякі тести почали падати в перший день місяця. Програміст розраховував, що в місяці завжди буде як мінімум 30 днів. У лютому його припущення не справдилося. &lt;/p&gt;

&lt;p&gt;Також згадується випадок, коли системні тести розраховували на наявність певних записів в базі даних. На великий подив, в цьому ж оточенні відбувалося ручне тестування програми, тому в один момент необхідні дані були видалені. І так як тест очікував знайти запис за унікальним ID, який генерувався автоматично, відновити дані було складно. Довелося виправляти декілька десятків тестів.&lt;/p&gt;

&lt;h2&gt;
  
  
  Що робити?
&lt;/h2&gt;

&lt;p&gt;Якщо тести залежать від випадкових величин на вході, ви рано чи пізно отримаєте проблеми. Краще попередьте їх, замінивши всі випадкові дані константами. До випадкових даних належать:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;дата і час&lt;/li&gt;
&lt;li&gt;дані специфічні для оточення (змінні оточення, назва комп'ютера, символ розмежування каталогів і т.ін.)&lt;/li&gt;
&lt;li&gt;файлова система&lt;/li&gt;
&lt;li&gt;мережа і бази даних&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Якщо ви звертаєтесь до зовнішніх залежностей напряму у коді, розгляньте можливість заміни їх на обгортки, які пізніше можна буде замінити стабами в автотестах. Наприклад, замість &lt;code&gt;DateTime.Now&lt;/code&gt; оголосіть власний інтерфейс з методом &lt;code&gt;Now&lt;/code&gt;, наприклад &lt;code&gt;IDateTimeService.Now&lt;/code&gt;. В тесті замініть його реалізацію на константу &lt;code&gt;new DateTime(2021, 2, 27)&lt;/code&gt; і отримаєте контрольовані вхідні дані. В реальному коді метод &lt;code&gt;Now&lt;/code&gt; буде викликати &lt;code&gt;DateTime.Now&lt;/code&gt; і нічого не зміниться.&lt;/p&gt;

&lt;p&gt;Досвідчені програмісти бачать наперед, чи можливо протестувати їх код. Якщо при написанні тестів ви переймаєтесь тим, як ваш код буде тестуватись далі, це свідчить про те, що практика тестування стала вашим надійним помічником і ви правильно користуєтесь цим інструментом.&lt;/p&gt;

&lt;p&gt;Якщо ви розраховуєте на наявність певних даних в зовнішніх, по відношенню до вашого коду, системах, розгляньте можливість створення цих даних перед початком тесту і видалення їх після його завершення.&lt;/p&gt;

&lt;h1&gt;
  
  
  Проблема №2. Тести дублюють логіку коду.
&lt;/h1&gt;

&lt;p&gt;Припустимо, у нас є математична формула, яку ми намагаємося реалізувати в коді. Наприклад, обчислення площі трикутника за його сторонами:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;CalculateAreaOfTriangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;При її тестування інтуїтивним підходом буде повторити формулу в тесті, щоб не обчислювати площу самостійно в калькуляторі. Але це не вірний підхід, бо програміст може допустити помилку, яка буде продубльована в тесті, особливо якщо один програміст пише і код, і тест до нього. Крім того, використання складних алгоритмів в тестах ускладнює їх підтримку.&lt;/p&gt;

&lt;p&gt;Кращим підходом буде розрахувати значення самостійно і використовувати в тесті константу. Також непоганим підходом може бути використання еталонної реалізації. Для прикладу вище, якщо ви маєте бібліотеку для роботи з геометрією, вона добре відома і протестована, то можна порівняти результати виконання функції &lt;code&gt;CalculateAreaOfTriangle&lt;/code&gt; з аналогічною функцією з цієї бібліотеки.&lt;/p&gt;

&lt;p&gt;Варто додати, що такого роду помилку я зустрічав раз чи двічі. В більшості проєктів, які я бачив, тести реалізовані вірно: константи використовуються як на вході, так і при перевірці тверджень.&lt;/p&gt;

&lt;h1&gt;
  
  
  Проблема №3. Тести не запускаються автоматично.
&lt;/h1&gt;

&lt;p&gt;Ця помилка теж не дуже поширена. Інколи буває, що програмісти тримають тести до коду, який вони написали, лише на своєму ПК, або тести знаходяться в репозиторії але їх необхідно запускати вручну. Або запуск тестів автоматизовано, але тригером є певна подія, ініційована людиною: наприклад, лідер команди QA натискає кнопку у веб-інтерфейсі.&lt;/p&gt;

&lt;p&gt;Загальним правилом є автоматичний запуск тестів при зміні коду: якщо змінився модуль А, то повинні бути запущені всі тести, що тестують модуль А (модульні тести), або тестують модулі А, Б і В як єдине ціле (інтеграційні тести). Інколи цю процедуру спрощують і запускають всі тести при будь-яких змінах в коді. Також можливий варіант, коли певні тести запускаються періодично зі сталим інтервалом. Результати таких тестів у випадку невдачі повинні доноситись певним чином до автора змін (обов'язково) і до всіх зацікавлених осіб (за бажанням).&lt;/p&gt;

&lt;p&gt;Але приховувати тести, або запускати їх вручну - поганий підхід. Відповідальна за це людина може відкласти це на декілька днів, а то й тижнів. Весь цей час невдалий тест буде чекати, щоб повідомити про знайдену помилку. А чим раніше помилку виявлено, тим легше її виправити.&lt;/p&gt;

&lt;h1&gt;
  
  
  Проблема №4. Тести залежать від інших тестів.
&lt;/h1&gt;

&lt;p&gt;При розробці системних (end-to-end) тестів виникає бажання їх спростити, перевикориставши результати, отримані з попередніх тестів. &lt;/p&gt;

&lt;p&gt;Наприклад, у вас є онлайн-магазин і ви бажаєте протестувати те, як працює додавання товару в кошик. Припустимо, додати можна тільки існуючий на сайті товар, будучи зареєстрованим користувачем. Отже, щоб провести тестування кошика, нам необхідно попередньо створити користувача і товар, увійти в систему і перейти на сторінку створеного товару. Тільки тут нам буде доступною кнопка, яка додає товар до кошику. &lt;/p&gt;

&lt;p&gt;Якщо у вас вже є готові тести для створення користувача і для додавання товару, а також тест для логіну, то виникає жагуче бажання в тестах для кошику використати результат роботи тестів реєстрації користувача і додавання товару. В такому разі програміст створює скрипт, в якому тести запускаються послідовно:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Реєстрація користувача&lt;/li&gt;
&lt;li&gt;Додавання товару&lt;/li&gt;
&lt;li&gt;Вхід на сайт&lt;/li&gt;
&lt;li&gt;Перехід на сторінку товару&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Додавання товару в кошик&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Тобто, наш тест залежить від даних, створених в результаті роботи інших 4 тестів. І ще добре, коли в документації вказано, які саме дані потрібні нашому тесту і як вони мають бути створені.&lt;/p&gt;

&lt;p&gt;Ви можете сказати, що це неправильно і так ніхто не робить! Адже, будь-яка помилка в попередніх тестах зробить неможливою перевірку кошику. Для прикладу, якщо не працює реєстрація, ми, насправді, не знаємо, чи можуть вже зареєстровані користувачі придбати щось в нашому магазині. Так, все вірно. Але я бачив такі тести досить часто в своїй практиці, в тому числі у всесвітньо відомих продуктах. &lt;/p&gt;

&lt;p&gt;Програміст, що написав такий код, може вам зауважити, що це економить час прогону тестів, адже для кожного нового тесту можна перевикористати вже доступні дані і не створювати/видаляти користувачів і товари кожен раз. Якщо врахувати, що дані зберігаються в досить повільних базах даних, це дійсно може суттєво економити час. &lt;/p&gt;

&lt;p&gt;Такий підхід може працювати, але не довго. Поки тестів мало і ними займається декілька людей, які добре контактують і слідкують за станом тестів, проблем може не виникати. Але, як тільки з'являються сотні тестів і їх підтримкою починають займатися декілька команд, ви гарантовано отримаєте безлад і затримки з постачанням продукту.&lt;/p&gt;

&lt;h2&gt;
  
  
  Що робити?
&lt;/h2&gt;

&lt;p&gt;Намагайтеся не писати автоматичних тестів, що залежать один від одного. Такі неочевидні залежності можуть коштувати дорого в майбутньому, коли тестів стає багато. Натомість, ви можете підготувати необхідні тестові дані у вигляді скрипту і перед кожним прогоном розгортати нову базу даних. Після того, як тести відпрацювали, ці дані повинні видалятись. Сьогодні доступні технології віртуалізації, що спрощують цей процес. Запуск &lt;a href="https://www.docker.com/"&gt;docker&lt;/a&gt; контейнерів займає секунди. Тобто, декілька секунд часу і ви маєте БД з підготовленими для тестів даними. Наслідком цього є те, що вам необхідно підтримувати цей скрипт. Він повинен стати частиною репозиторію і змінюватись разом зі зміною структури даних.&lt;/p&gt;

&lt;p&gt;До того ж, незалежні тести можуть виконуватись паралельно і таким чином працювати навіть швидше, ніж тести, що виконуються один за одним в чіткій послідовності.&lt;/p&gt;

&lt;h1&gt;
  
  
  Проблема №5. Тести неповні.
&lt;/h1&gt;

&lt;p&gt;При написанні модульних тестів, зазвичай, задаються вхідні дані і перевіряються вихідні. Якщо ми тестуємо складні класи, що мають безліч залежностей, не завжди очевидно, що є результатом виконання системи під тестом. &lt;/p&gt;

&lt;p&gt;Уявіть, що у нас є метод, що видаляє файл. Він перевіряє права доступу, далі звертається до сервісу файлів і логує результат операції. Всі сервіси, використані в цьому методі, є зовнішніми залежностями, функціонал яких ми не будемо тестувати. В коді тесту вони будуть існувати у вигляді моків.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DeleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_permissionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_fileService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"File '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' was successfully deleted."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Вхідними даними для цього методу будуть:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;параметр &lt;code&gt;fileName&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;успішність виконання методу &lt;code&gt;CheckPermissions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;успішність виконання методу &lt;code&gt;DeleteFile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;успішність виконання методу &lt;code&gt;LogInformation&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Вихідними даними для методу будуть:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;факт виклику методу &lt;code&gt;CheckPermissions&lt;/code&gt;  і назва операції, для якої перевіряються права&lt;/li&gt;
&lt;li&gt;факт виклику методу &lt;code&gt;DeleteFile&lt;/code&gt; та ім'я файлу, що видаляється&lt;/li&gt;
&lt;li&gt;факт виклику методу &lt;code&gt;LogInformation&lt;/code&gt; і рядок тексту, що логується&lt;/li&gt;
&lt;li&gt;скільки разів викликались зазначені вище методи&lt;/li&gt;
&lt;li&gt;чи викликались будь які інші методи будь яких сервісів&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Тобто, вищенаведений код залежить не тільки від вхідних параметрів, але також від успішності виконання всіх методів. При тестуванні ми повинні задати всі вхідні дані і перевірити всі вихідні. Тільки у такому разі наш тест буде повним.&lt;/p&gt;

&lt;p&gt;Марк Сіманн у своїй статті &lt;a href="https://blog.ploeh.dk/2017/02/02/dependency-rejection/"&gt;Dependency rejection&lt;/a&gt; називає ці данні &lt;em&gt;непрямий вхід і вихід&lt;/em&gt; (indirect input and output). Але це такі самі вхідні та вихідні дані, як і параметри методу та його результат. Не можна їх ігнорувати, інакше не можна гарантувати, що код працює як належить.&lt;/p&gt;

&lt;p&gt;Я часто бачив, як навіть досвідчені колеги ігнорували і не перевіряли частину вихідних даних, або не переймалися над тим, щоб задати частину вхідних даних. Пояснень я чув декілька: &lt;em&gt;"тест повинен тестувати лише одну річ"&lt;/em&gt; і &lt;em&gt;"ми не перевіряємо те, що не важливо для логіки програми"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;З першим твердженням я погоджуюсь, тестувати потрібно лише одну річ за раз. Але "одна річ" значить не один &lt;code&gt;Assert&lt;/code&gt; в кінці тесту. Одна річ - це один виклик тестуємого методу, або, іншими словами, один набір вхідних даних. Перевіряючи лише частинку вихідних даних, ми не гарантуємо коректність роботи методу для даного випадку.&lt;/p&gt;

&lt;p&gt;Якщо програміст каже, що не тестує те, що не важливо, можна запитати, для чого він додав неважливий код взагалі? Часто програмісти ігнорують перевірку запису логів. Логи програми, можливо, не важливі для програміста зараз і їх навряд чи можна назвати частиною бізнес логіки. Але логи точно стають важливими, коли код не працює у користувача. Коли аналіз логів - єдиний спосіб знаходження дефекту, вони надзвичайно важливі.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Не варто плутати "неповні тести" і "неповне покриття тестами". Неповне покриття означає, що деякі ділянки коду не задіяні в тестах. Це може бути припустимим, якщо це якісь унікальні випадки, кількість можливих тестових сценаріїв завелика і ваша команда не прагне до 100 % покриття.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Що робити?
&lt;/h2&gt;

&lt;p&gt;Зважаючи на все це, можна зробити висновок, що код тесту буде об'ємнішим і складнішим, ніж код методу, що тестується. Часто так і є, особливо, коли клас має багато зовнішніх залежностей. Але це ціна, яку ми платимо за використання механізму ін'єкції залежностей (DI). Ви можете спростити собі життя, використовуючи бібліотеки для моків і фейків, наприклад &lt;a href="https://github.com/Moq/moq4/wiki/Quickstart"&gt;Moq&lt;/a&gt; або &lt;a href="https://nsubstitute.github.io/"&gt;NSubstitude&lt;/a&gt;. Але інколи все ж дисципліни в програмістів не вистачає і тоді ми приходимо до наступної, ще більшої проблеми з тестами.&lt;/p&gt;

&lt;h1&gt;
  
  
  Проблема №6. Тести написані неохайно.
&lt;/h1&gt;

&lt;p&gt;Під &lt;em&gt;неохайністю&lt;/em&gt; я маю на увазі, що код тестів написаний нашвидкоруч, без належної уваги. Дехто вважає, що код тестів - це щось другорядне і не варто йому приділяти багато уваги. Я з цим не згоден. Тести - це те, що забезпечує якість вашого коду. Якщо вам не важлива якість, тоді так, тести - другорядна річ і не варто витрачати зусилля, підтримуючи їх в належній формі. Але якщо ви вирішили писати тести і якість продукту для вас має значення, то варто підтримувати їх в такому ж стані, як і основний код. Це значить, що тести мають легко читатися, не мають містити дубльованого коду і, взагалі, підхід повинен бути точно такий як і до основного коду вашого продукту.&lt;/p&gt;

&lt;p&gt;Я бачив ситуації, коли погано організовані тести потрапили в руки нової людини і та, замість того щоб розібратись і покращити їх, просто закоментувала код (також це можна зробити завдяки атрибутам типу &lt;code&gt;[Ignore]&lt;/code&gt;). Далі такий тест може бути просто забутий і контроль якості буде ослаблений.&lt;/p&gt;

&lt;h2&gt;
  
  
  Що робити?
&lt;/h2&gt;

&lt;p&gt;Не варто зневажливо ставитись до тестів. Якщо їх проблематично змінювати, проведіть рефакторинг, точно так, як ви б зробили в основному коді. Закладайте час на написання тестів (модульних чи інтеграційних) при плануванні задач. Якщо ваш керівник просить знехтувати тестами, щоб пришвидшити розробку, запитайте, чи готовий він пожертвувати якістю продукту і темпом розробки в майбутньому, адже код без тестів змінювати страшно.&lt;/p&gt;

&lt;h1&gt;
  
  
  Проблема №7. Тести відсутні.
&lt;/h1&gt;

&lt;p&gt;Можливо, для вас це звучить, як щось неймовірне? Впевнений, що для більшості це так. Але й досі існують софтверні компанії, які ігнорують тестування, що проводиться програмістами. Зазвичай, вони мають відокремлений підрозділ тестувальників, і відповідальність за якість продукту покладається на нього. &lt;/p&gt;

&lt;p&gt;В таких компаніях, зазвичай, пишуть неякісний код і продукт випускається з безліччю дефектів. Задачі по розробці нових функції багато разів кочують з робочого столу програміста до робочого столу QA і назад. Випуск продукту здійснюється максимум раз в рік, а відділи тестувальників і програмістів часто в стані конфлікту.&lt;/p&gt;

&lt;p&gt;Інколи мови програмування або фреймворки не полегшують можливості модульного тестування. Наприклад, мови Delphi або C++ мають дуже обмежені можливості рефлексії, створення фейків, моків і стабів. В цих мовах створення тестів для складного коду з безліччю залежностей може бути вкрай витратним процесом, тому модульного тестування уникають.&lt;/p&gt;

&lt;p&gt;Крім того, часто програмісти не тестують свій код в компаніях, де історично склалась така культура. Інколи такі компанії були засновані не програмістами, а, наприклад, бухгалтерами або інженерами-електронщиками.&lt;/p&gt;

&lt;h2&gt;
  
  
  Що робити?
&lt;/h2&gt;

&lt;p&gt;Ми, як професіонали своєї справи, маємо переживати за результат своєї праці. Тестування забезпечує якість і надійність, тож не ігноруйте його. Не треба розраховувати, що ваш колега QA знайде всі дефекти. Такий підхід веде до неохайного коду і програміст з часом деградує. &lt;/p&gt;

&lt;p&gt;Спонукайте вашого керівника до переходу на інші, сучасні мови програмування, попросіть його знайти час на впровадження фреймворків тестування, які б полегшили вам роботу. Якщо ваш безпосередній начальник не реагує, зверніться безпосередньо до керівництва компанії. В сучасному світі продукт, що тестується поверхово, не має майбутнього і може призвести до репутаційних та матеріальних втрат компанією.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.martyniuk.dev/uk/posts/auto-tests-issues/"&gt;Оригінал статті&lt;/a&gt; на моєму сайті.&lt;/p&gt;

</description>
      <category>autotests</category>
      <category>ukrainian</category>
      <category>cleancode</category>
      <category>unittests</category>
    </item>
    <item>
      <title>Транзакції в DynamoDB</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Sat, 03 Oct 2020 09:40:42 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/dynamodb-5gpc</link>
      <guid>https://dev.to/olesmartyniuk/dynamodb-5gpc</guid>
      <description>&lt;p&gt;Протягом багатьох років в розробці програмного забезпечення домінували реляційні бази даних. Мова SQL стала однією з найпоширеніших мов програмування. Але на початку 21 століття розвиток WEB 2.0 і потреби таких компаній як Google і Facebook спричинили революцію в збереженні даних. &lt;/p&gt;

&lt;p&gt;Сувора узгодженість і внутрішнє об'єднання даних стали менш важливими за високу доступність, швидкість та можливість горизонтального масштабування. З часом сформувалась стійка думка, що NoSQL бази даних - це для не впорядкованих даних великих розмірів, що необхідно зберігати в кластері та швидко отримувати. Тоді як SQL - це для структурованих даних, які пов'язані відношеннями і повинні бути суворо узгодженими завдяки механізму транзакцій та зовнішніх ключів. &lt;/p&gt;

&lt;p&gt;Перед проектування систем програмісти почали запитувати себе: що важливіше - &lt;a href="https://uk.wikipedia.org/wiki/ACID"&gt;ACID&lt;/a&gt; чи висока доступність та швидкість? Така ситуація тривала деякий час, аж поки в NoSQL не з'явились транзакції. &lt;/p&gt;

&lt;p&gt;В цій статті я розкажу про транзакції в одній з перших NoSQL баз даних - Amazon DynamoDB. Подивимось чим вони відрізняються від транзакцій в SQL, в яких випадках варто будувати програми з їх використанням і як працювати з ними в C# та .NET Core.&lt;/p&gt;

&lt;h1&gt;
  
  
  DynamoDB
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/dynamodb/"&gt;DynamoDB&lt;/a&gt; - документна база даних без схеми. Вона зберігає дані в таблицях, кожна з яких може розміщуватись на декількох серверах, розподіляючи таким чином навантаження. Це дозволяє DynamoDB обробляти мільйони запитів за секунду в пікові періоди.&lt;/p&gt;

&lt;p&gt;Для представлення документів DynamoDB використовує формат JSON. Створення таблиці вимагає лише трьох аргументів: імені таблиці, ключа та списку атрибутів, серед яких повинен бути атрибут, що використовується як &lt;em&gt;ключ секції&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Ключ секції (Partition Key) використовується для визначення фактичного розміщення запису. Застосовуючи HASH-функцію до ключа секції DynamoDB знаходить фізичний сервер в кластері і місце на сервері куди дані будуть записані. Ключ секції разом з необов'язковим ключем сортування (Sort Key) створюють &lt;em&gt;первісний ключ&lt;/em&gt;, що дозволяє унікально ідентифікувати запис в таблиці DynamoDB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kiGeqD-M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/dynamodb-table.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kiGeqD-M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/dynamodb-table.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Тоді як реляційні бази даних пропонують для запитів досить потужну мову SQL, DynamoDB пропонує лише операції &lt;code&gt;Put&lt;/code&gt;, &lt;code&gt;Get&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt; та &lt;code&gt;Delete&lt;/code&gt; на одиночних таблицях і взагалі не пропонує можливості об'єднання таблиць. Зате через цю простоту DynamoDB добре масштабується і має високу пропускну здатність. &lt;/p&gt;

&lt;p&gt;Ще однією особливістю БД є те, що її використання тарифікується не за місцем, яке займають дані, а за пропускною здатністю, що вимірюється так званими &lt;code&gt;RCU&lt;/code&gt; та &lt;code&gt;WCU&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;RCU&lt;/code&gt; (read capacity unit) - це одиниця, що відповідає одному запиту на читання до 4 Kb даних. &lt;code&gt;WCU&lt;/code&gt; (write capacity unit) - аналогічно для запису, тільки ліміт даних - 1 Kb.&lt;/p&gt;

&lt;h1&gt;
  
  
  Локальна DynamoDB
&lt;/h1&gt;

&lt;p&gt;Давайте спробуємо запустити DynamoDB локально і виконати прості запити для створення таблиці і запису даних.&lt;/p&gt;

&lt;p&gt;Для роботи нам знадобляться &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; та &lt;a href="https://dotnet.microsoft.com/download"&gt;.NET Core SDK&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Amazon пропонує локальну версію DynamoDB у вигляді Docker образу. Вона повністю підтримує транзакції, тому акаунт AWS нам не потрібен, далі все будемо робити на локальному комп'ютері.&lt;/p&gt;

&lt;p&gt;Відкриємо консоль і запустимо DynamoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8000:8000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;amazon/dynamodb-local&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Initializing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DynamoDB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;following&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configuration:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Port:&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nx"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;InMemory:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;DbPath:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nx"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;SharedDb:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;shouldDelayTransientStatuses:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;CorsParams:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker завантажив образ &lt;code&gt;dynamodb-local&lt;/code&gt; і запустив сервіс на порту &lt;code&gt;8000&lt;/code&gt;. Тепер ми можемо звернутися до бази даних за адресою &lt;code&gt;http://localhost:8000&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Дані будуть збережені в пам'яті, про що свідчить параметр &lt;code&gt;InMemory: true&lt;/code&gt;, тому &lt;code&gt;DbPath&lt;/code&gt; (шлях до файлу даних) порожній. Якщо ви бажаєте зберігати дані на диску між запусками контейнеру вам &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.UsageNotes.html"&gt;необхідно вказати параметри&lt;/a&gt; &lt;code&gt;-sharedDB&lt;/code&gt; та &lt;code&gt;-dbPath&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Система замовлення таксі
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---zenQLTb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-illustration.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---zenQLTb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-illustration.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Перед тим як створити першу таблицю, опишемо предметну область, яку ми будемо моделювати.&lt;/p&gt;

&lt;p&gt;Припустимо у нас є система замовлення таксі. Є клієнт, водій і, власне, замовлення. Опишемо деякі вимоги до нашої системи.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Коли клієнт замовляє поїздку створюється замовлення.&lt;/li&gt;
&lt;li&gt;Водій автомобіля приймає замовлення до роботи.&lt;/li&gt;
&lt;li&gt;Водій не може прийняти замовлення, яке вже в роботі.&lt;/li&gt;
&lt;li&gt;Водій не може прийняти замовлення, якщо він виконує інше замовлення.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Це дуже спрощена схема роботи таких сервісів, як Uber або Uklon. Звісно, можна придумати набагато більше вимог до такої системи, але для нас зараз вони не важливі. Нам важливо продемонструвати &lt;strong&gt;ідемпотентність&lt;/strong&gt;, &lt;strong&gt;узгодженність&lt;/strong&gt; та &lt;strong&gt;атомарність&lt;/strong&gt; операцій з DynamoDB. &lt;/p&gt;

&lt;p&gt;Отже, які таблиці нам знадобляться в базі даних?&lt;/p&gt;

&lt;p&gt;Перша - &lt;em&gt;клієнт&lt;/em&gt;. Будь яка таблиця DynamoDB повинна містити унікальний ключ, нехай це буде телефонний номер клієнта. Також таблиця буде містити поточне замовлення цього клієнту. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LcU2UzLK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/client-table.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LcU2UzLK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/client-table.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;У &lt;em&gt;водія&lt;/em&gt; все схоже, тільки як унікальний ідентифікатор візьмемо номер машини. Так як модель водія дуже схожа на модель клієнта, то чому б нам не записати їх в одну таблицю? Назвемо її &lt;code&gt;Taxi&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;В &lt;em&gt;замовленні&lt;/em&gt; будуть міститись ідентифікатори водія, клієнта і статус замовлення: &lt;code&gt;Pending&lt;/code&gt;, &lt;code&gt;InProgress&lt;/code&gt;, &lt;code&gt;Done&lt;/code&gt;. Замовлення також будемо зберігати в таблиці &lt;code&gt;Taxi&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OHzNbmC4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-table.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OHzNbmC4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-table.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Уявімо, що в системі існує клієнт, водій та замовлення, яке вже знаходиться в статусі очікування.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KwF1Cdmh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-table-1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KwF1Cdmh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-table-1.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Для розробників, які багато працювали з SQL така загальна таблиця може здатись дивною. Їм відразу захочеться розділити її та провести нормалізацію. Але у світі NoSQL це абсолютно звична річ. Такі таблиці називаються &lt;em&gt;гомогенними&lt;/em&gt;. DynamoDB не буде марнувати місце на диску для збереження порожніх полів записів, адже вона збергіає документи, або колекції атрибутів. Amazon радить використовувати саме гомогенні таблиці в DynamoDB. Їх рекомендація - &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html#bp-general-nosql-design-concepts"&gt;тримати пов'язані дані якомога ближче і мати мінімальну кількість таблиць&lt;/a&gt;. &lt;/p&gt;

&lt;h1&gt;
  
  
  AWS SDK та .NET Core
&lt;/h1&gt;

&lt;p&gt;Створимо консольну програму в .NET Core та додамо пакет для роботи з DynamoDB API - &lt;code&gt;AWSSDK.DynamoDBv2&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Restore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;succeeded.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AWSSDK.DynamoDBv2&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PackageReference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'AWSSDK.DynamoDBv2'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'3.5.0.22'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;У файлі &lt;code&gt;Program.cs&lt;/code&gt; створимо клієнта для роботи з локальною базою даних і додамо метод для створення таблиці:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AmazonDynamoDBClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AmazonDynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AmazonDynamoDBConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServiceURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8000"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{3e80b07d-e2e6-4310-8fda-851296a17a10}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;driverId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"АК9265АК"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0993832478"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Taxi"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateTableAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CreateTableRequest&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AttributeDefinitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AttributeDefinition&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeDefinition&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;AttributeName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;AttributeType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"S"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;KeySchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeySchemaElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeySchemaElement&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;AttributeName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;KeyType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"HASH"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ProvisionedThroughput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProvisionedThroughput&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ReadCapacityUnits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;WriteCapacityUnits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Код проекту можна знайти на &lt;a href="https://github.com/alexmartyniuk/blog-dynamodb-transactions/"&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Єдиним атрибутом який ми визначили є &lt;code&gt;Id&lt;/code&gt; типу &lt;code&gt;String&lt;/code&gt;. В схемі вказано, що поле &lt;code&gt;Id&lt;/code&gt; буде ключем секції. Також необхідно вказати очікувану пропускну здатність, щоб DynamoDB знала як правильно масштабуватись. Для нас це не суттєво, нехай буде 5 одиниць за секунду для читання і запису.&lt;/p&gt;

&lt;p&gt;Додамо клієнта, водія та замовлення як показано в таблиці вище. Використаємо нетранзакційний метод &lt;code&gt;PutItem&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AddClientDriverAndOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PutItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PutItemRequest&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"OrderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PutItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PutItemRequest&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driverId&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PutItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PutItemRequest&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"ClientId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"OrderStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Pending"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Транзакція в дії
&lt;/h1&gt;

&lt;p&gt;Спробуймо реалізувати якусь більш складну операцію, наприклад, &lt;em&gt;водій бере замовлення у роботу&lt;/em&gt;. Для того, щоб водій взяв замовлення нам необхідно:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;оновити рядок замовлення: 

&lt;ul&gt;
&lt;li&gt;записати номер машини водія в поле &lt;code&gt;DriverId&lt;/code&gt;, якщо воно порожнє (водій не може взяти замовлення, яке вже виконується)&lt;/li&gt;
&lt;li&gt;записати &lt;code&gt;InProgress&lt;/code&gt; в поле &lt;code&gt;OrderStatus&lt;/code&gt;, якщо воно було &lt;code&gt;Pending&lt;/code&gt; (водій не може взяти замовлення в будь якому іншому статусі окрім "Очікує виконання")&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;оновити рядок водія:

&lt;ul&gt;
&lt;li&gt;записати номер замовлення в поле &lt;code&gt;OrderId&lt;/code&gt;, якщо воно порожнє (водій не може взяти одночасно два замовлення)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Тобто, кінцевий результат повинен виглядати так:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1mvbu3Qx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-table-2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1mvbu3Qx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.martyniuk.info/assets/img/posts/2020-09-28-use-dynamodb-transactions-with-dotnet-core/taxi-table-2.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Тут нам знадобиться &lt;a href="https://uk.wikipedia.org/wiki/ACID"&gt;ACID&lt;/a&gt; транзакція, адже оновити записи замовлення і водія потрібно одночасно. Якщо будь яка з перелічених вище умов не виконається, жодних змін в базі не має відбутися.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;CreateTable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;AddClientDriverAndOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;AssignOrderToDriver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;AssignOrderToDriver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TransactWriteItemsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TransactWriteItemsRequest&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TransactItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransactWriteItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TransactWriteItem&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;TableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driverId&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="n"&gt;UpdateExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"set OrderId = :OrderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;ConditionExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"attribute_not_exists(OrderId)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;ExpressionAttributeValues&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;":OrderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TransactWriteItem&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;TableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="n"&gt;UpdateExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"set DriverId = :DriverId, OrderStatus = :NewStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;ConditionExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"attribute_not_exists(DriverId) AND OrderStatus=:OldStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;ExpressionAttributeValues&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;":DriverId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driverId&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;":OldStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Pending"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;":NewStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"InProgress"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ClientRequestToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"IdempotencyToken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В цьому коді ми виконуємо транзакцію, що складається з двох елементів &lt;code&gt;TransactWriteItem&lt;/code&gt;. Якщо умова в &lt;code&gt;ConditionExpression&lt;/code&gt; виконується, буде виконано вираз &lt;code&gt;UpdateExpression&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Для запису водія ми оновлюємо номер замовлення, якщо воно порожнє:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="n"&gt;UpdateExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"set OrderId = :OrderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ConditionExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"attribute_not_exists(OrderId)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Для запису замовлення ми оновлюємо поле водія та статус, якщо водій ще не був призначений, а статус дорівнює &lt;code&gt;Pending&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="n"&gt;UpdateExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"set DriverId = :DriverId, OrderStatus = :NewStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ConditionExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"attribute_not_exists(DriverId) AND OrderStatus=:OldStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Операція, що дозволяє водієві взяти замовлення, відповідає нашим трьом вимогам:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Вона &lt;strong&gt;ідемпотентна&lt;/strong&gt;. Повторне виконання методу не змінить стан бази даних і не викличе помилку. За це відповідає атрибут &lt;code&gt;ClientRequestToken&lt;/code&gt;, який по суті є токеном ідемпотентності. Всі наступні запити до БД з тим самим &lt;code&gt;ClientRequestToken&lt;/code&gt; будуть проігноровані. Це дозволяє, наприклад, водієві помилково натиснути два рази на кнопку прийняття замовлення.&lt;/li&gt;
&lt;li&gt;Вона &lt;strong&gt;узгоджена&lt;/strong&gt;. Записи замовлення та водія змінюються синхронно таким чином, що водій не може взяти замовлення в іншому статусі окрім &lt;code&gt;Pending&lt;/code&gt;. Замовлення в статусі &lt;code&gt;Pending&lt;/code&gt; завжди буде мати пов'язаного водія.&lt;/li&gt;
&lt;li&gt;Вона &lt;strong&gt;атомарна&lt;/strong&gt;. Запит або буде виконано повністю, або жодних змін до бази даних застосовуватись не буде.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Причини невдачі транзакцій
&lt;/h1&gt;

&lt;p&gt;Якщо виконати даний метод але з некоректними даними (наприклад, водій намагається взяти в роботу чуже замовлення) він викличе помилку:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Amazon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DynamoDBv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionCanceledException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt; &lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;please&lt;/span&gt; &lt;span class="n"&gt;refer&lt;/span&gt; &lt;span class="n"&gt;cancellation&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;specific&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Транзакція може бути відмінена з декількох причин:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;не виконується умова в &lt;code&gt;ConditionExpression&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;одночасно виконується інша транзакція з тим самим записом &lt;/li&gt;
&lt;li&gt;недостатня пропускна здатність (в нашому випадку це більше 5 викликів на секунду)&lt;/li&gt;
&lt;li&gt;недостатньо прав для виконання запиту (це стосується лише DynamoDB в AWS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Що робити, коли ви отримали помилку виконання транзакції? Варто спробувати виконати операцію повторно. Переконайтесь, що ви встановили токен ідемпотентності (поле &lt;code&gt;ClientRequestToken&lt;/code&gt;), тоді SDK може спробувати самостійно повторити запит.&lt;/p&gt;

&lt;p&gt;Якщо ж ваша транзакція після автоматичних спроб не вдалась, необхідно виконати повторну спробу на рівні логіки програми. Для цього необхідно перебудувати вихідний стан вашої програми і спробувати повторити транзакцію з новими умовами і новим бажаним кінцевим станом БД. &lt;/p&gt;

&lt;p&gt;Для отримання нового вихідного стану вашої програми (синхронізація з базою даних) можна обрати один з трьох методів:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure.ALL_OLD&lt;/code&gt; - встановленя цього поля дозволяє при виконанні транзакції повернути значення атрибутів, якщо умова транзакції не була виконана.&lt;/li&gt;
&lt;li&gt;Виконати &lt;code&gt;TransactionGetItems&lt;/code&gt;, тобто транзакційно отримати всі необхідні дані самостійно.&lt;/li&gt;
&lt;li&gt;Працювати далі за умови, що узгодженість даних буде врешті-решт досягнута, можливо, після виконання повторного запиту.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Порівняння з SQL
&lt;/h1&gt;

&lt;p&gt;В SQL і деяких інших NoSQL рішеннях, таких як MongoDB, транзакції реалізовані в розмовному стилі. Першим викликом транзакція відкривається (&lt;code&gt;BEGIN TRANSACTION&lt;/code&gt;), потім окремими викликами здійснюється модифікація даних (&lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;). В кінці транзакція закривається (&lt;code&gt;COMMIT TRANSACTION&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Такий розподіл координації між клієнтом і сервером накладає деякі обмеження на транзакції і робить їх реалізацію більш складною, а виконання - повільнішим. &lt;/p&gt;

&lt;p&gt;Транзакції DynamoDB реалізовані в рамках &lt;em&gt;одного виклику API&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Транзакції DynamoDB працюють виключно на сервері і у клієнта немає контролю за початком транзакції, її підтвердженням або відміною. Це робить транзакції DynamoDB дуже швидкими і розробнику не потрібно обирати між транзакційною БД і БД, яка масштабується горизонтально.&lt;/p&gt;

&lt;p&gt;Також, з транзакціями DynamoDB неможливі взаємні блокування, оскільки використовується оптимістичний контроль одночасності. Завдяки цьому забезпечується низька затримка та висока доступність.&lt;/p&gt;

&lt;h1&gt;
  
  
  Недоліки транзакцій
&lt;/h1&gt;

&lt;p&gt;Але в транзакцій DynamoDB є ряд обмежень, які варто врахувати:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Транзакції працюють лише в рамках одного регіону і одного акаунту AWS.&lt;/li&gt;
&lt;li&gt;Транзакції можуть містити до десяти елементів, тобто можна оновити або додати не більше 10 записів за раз.&lt;/li&gt;
&lt;li&gt;Транзакції споживають в два рази більше &lt;code&gt;RCU&lt;/code&gt; або &lt;code&gt;WCU&lt;/code&gt;, так як всередині використовуюється двофазний механізм: після виконання транзакції дані зчитуються ще раз для підтвердження їх коректності. Через це транзакційні виклики будуть в два рази дорожчими для споживача.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Висновок
&lt;/h1&gt;

&lt;p&gt;Транзакції в DynamoDB допомагають розробникам:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;підтримувати коректність даних, вносячи зміни в декілька записів і таблиць одночасно;&lt;/li&gt;
&lt;li&gt;спрощувати бізнес логіку програм, переносячи на сервер перевірку даних;&lt;/li&gt;
&lt;li&gt;оновлювати дані в багатьох таблицях узгоджено.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Транзакції DynamoDB повністю підтримують ACID і в той же час можуть необмежено масштабуватись для забезпечення низької затримки та високої доступності даних.&lt;/p&gt;

&lt;p&gt;Для .NET розробників Amazon пропонує низькорівневий SDK для доступу до DynamoDB, який повністю підтримує транзакції. Його використання дещо багатослівне і може заплутати новачка, але якщо ви плануєте працювати з великими навантаженнями на базу даних і в той же час не хочете позбавляти себе ACID транзакцій, DynamoDB може бути хорошим вибором.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Всі приклади коду можна знайти на &lt;a href="https://github.com/alexmartyniuk/blog-dynamodb-transactions/"&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Оригінальна стаття на &lt;a href="https://martyniuk.dev/uk/posts/transactions-dynamodb/"&gt;моєму сайті&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>dynamodb</category>
      <category>transaction</category>
      <category>ukrainian</category>
    </item>
    <item>
      <title>Безкоштовна БД для програми ASP.NET Core</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Wed, 29 Jul 2020 18:17:02 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/asp-net-core-2l86</link>
      <guid>https://dev.to/olesmartyniuk/asp-net-core-2l86</guid>
      <description>&lt;p&gt;В цій статті я розкажу про додатки &lt;strong&gt;&lt;a href="http://heroku.com" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;&lt;/strong&gt;, ми створимо базу даних &lt;strong&gt;&lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;&lt;/strong&gt; і налаштуємо її для підтримки процесу аутентифікації у веб-програмі ASP.NET Core. Це друга стаття циклу, тому варто ознайомитись з &lt;a href="https://dev.to/alexmartyniuk/net-core-webapp-14p6"&gt;попередньою&lt;/a&gt;, в якій проєкт було створено і розгорнуто. Створена база даних не буде вимагати жодних фінансових витрат і гарно підходить для власного невеликого проєкту.&lt;/p&gt;

&lt;h2&gt;
  
  
  Передумови
&lt;/h2&gt;

&lt;p&gt;Для успішного виконання наступних кроків вам необхідно зареєструвати акаунт в &lt;a href="http://heroku.com" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;, якщо ви цього ще не зробили, та встановити такі інструменти:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/download" rel="noopener noreferrer"&gt;.NET Core SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devcenter.heroku.com/articles/heroku-cli" rel="noopener noreferrer"&gt;Heroku CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Вся робота буде відбуватись в командному рядку. У якості редактору коду можна використовувати будь-який на ваш вибір.&lt;/p&gt;

&lt;p&gt;У &lt;a href="https://dev.to/alexmartyniuk/net-core-webapp-14p6"&gt;попередній статті&lt;/a&gt; ми створили просту програму ASP.NET Core, налаштували її для роботи в Heroku і розгорнули у хмарі. В цій статті ми модифікуємо створений код: додамо аутентифікацію та базу даних для зберігання інформації про користувачів. Готовий код можна знайти на &lt;a href="https://github.com/alexmartyniuk/blog-dotnet-app-heroku" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Додатки Heroku
&lt;/h2&gt;

&lt;p&gt;Heroku має безліч готових інструментів та сервісів, які називаються &lt;strong&gt;Add-ons&lt;/strong&gt; (додатки). На &lt;a href="https://elements.heroku.com/addons" rel="noopener noreferrer"&gt;сторінці з додатками&lt;/a&gt; можна знайти біля 150 сервісів, згрупованих по категоріям: Data Stores, Monitoring, Logging, Caching і т.д. Наприклад, тут є бази даних MySQL, Redis або MongoDB, сервіси повнотекстового пошуку Elasticsearch, стримінгу повідомлень Kafka, генерації PDF, обробки відео і багато іншого.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fheroku-addons.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fheroku-addons.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ми будемо використовувати сервіс бази даних, який називається &lt;a href="https://elements.heroku.com/addons/heroku-postgresql" rel="noopener noreferrer"&gt;Heroku Postgres&lt;/a&gt;. Він доступний в декількох планах: від найпростішого &lt;strong&gt;Hobby Dev&lt;/strong&gt;, який обмежений 20-ти одночасними з'єднаннями і 10000-ми рядками, до найбільш потужного &lt;strong&gt;Shield 8&lt;/strong&gt;, який надає 488 GB оперативної пам'яті і 3TB сховища. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fpostgresql-pricing.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fpostgresql-pricing.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Масштабування Heroku Postgres
&lt;/h2&gt;

&lt;p&gt;Heroku Postgres легко масштабується вертикально. Є можливість збільшувати розмір сховища і оперативної пам'яті, в якій знаходиться так званий &lt;code&gt;hot-data-set&lt;/code&gt;, для швидшої оброки запитів. Для вертикального масштабування необхідно просто змінити план використання на вищий. Горизонтальне масштабування в Heroku Postgres можливе завдяки спеціальній конфігурації &lt;code&gt;leader-follower&lt;/code&gt;. Вона дозволяє створювати декілька копій вашої бази даних, доступних лише для читання, які називаються &lt;code&gt;follower&lt;/code&gt;. Дані в цих БД синхронізуються в реальному часі із основною базою, яка в термінології Heroku називається &lt;code&gt;leader&lt;/code&gt;. Heroku забезпечує розташування баз даних &lt;code&gt;follower&lt;/code&gt; та &lt;code&gt;leader&lt;/code&gt; в різних дата-центрах, що підвищує їх надійність і дає можливість продовжити роботу вашій програмі у випадку виходу з ладу частини інфраструктури Heroku.&lt;/p&gt;

&lt;p&gt;Нам цілком підйде план Hobby Dev, адже &lt;strong&gt;10000&lt;/strong&gt; рядків - це більше ніж достатньо для демострації аутентифікації у веб-програмі. Для даного плану Heroku забезпечує доступність БД на рівні 99.5%. Для вищих планів доступність вища і сягає 99.95%.&lt;/p&gt;

&lt;p&gt;Варто додати, що Heroku Postgres доступна у двох регіонах - Північній Америці та Європі. Ми створимо БД у Європі, так як наша веб-програма також розміщена на сервері у Європі.&lt;/p&gt;

&lt;h2&gt;
  
  
  Створення бази даних
&lt;/h2&gt;

&lt;p&gt;Перейдіть в каталог з проєктом &lt;code&gt;dotnet-app-heroku&lt;/code&gt;, що був створений в &lt;a href="https://dev.to/alexmartyniuk/net-core-webapp-14p6"&gt;попередній статті&lt;/a&gt;. Перш за все, необхідно увійти в акаунт Heroku:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heroku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;heroku:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Press&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exit:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;Opening&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://cli-auth.heroku.com/auth/cli/browser/9e58d2b7-dd08-4dca-968e-5ef0ddaf399d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Logging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Logged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elexander&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;heroku&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;ukr.net&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Створимо базу даних, вказавши хобі план та ім'я нашої програми в Heroku&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heroku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;addons:create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;heroku-postgresql:hobby-dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet-app-heroku&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Creating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;heroku-postgresql:hobby-dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;⬢&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet-app-heroku...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;free&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;been&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;postgresql-horizontal-81849&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;heroku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;addons:docs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;heroku-postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;documentation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Heroku записав інформацію про підключення до БД в змінну оточення &lt;code&gt;DATABASE_URL&lt;/code&gt;. Пізніше ми зчитаємо її під час виконання програми і таким чином отримаємо інформацію про адресу серверу, ім'я користувача, його пароль і назву бази даних на сервері Postgres. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ви не можете самостійно визначити параметри підключення, як ім'я користувача чи пароль, адже Heroku не надає вам окремий сервер бази даних, а дозволяє використовувати сервер спільно з іншими користувачами. До того ж, Heroku може змінити дані підключення, наприклад, адресу серверу, але в такому разі змінна оточення &lt;code&gt;DATABASE_URL&lt;/code&gt; також буде оновлена. Враховуючи це, не варто зберігати рядок підключення в якомусь іншому місці, а тим більше у вихідному коді програми.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Перевіримо стан нашої бази даних і переконаймося, що план підключення дійсно не вимагає жодних витрат (Price = free):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heroku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;addons&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Owning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nx"&gt;Add-on&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="nx"&gt;Plan&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="nx"&gt;Price&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="err"&gt;─────────────────&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;───────────────────────────&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;───────────────────────────&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;─────&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;───────&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet-app-heroku&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;postgresql-horizontal-81849&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;heroku-postgresql:hobby-dev&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;free&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Налаштування програми для роботи з БД
&lt;/h2&gt;

&lt;p&gt;Тепер, коли база даних створена, необхідно додати аутентифікацію в нашу програму ASP.NET Core. Нам знадобляться додаткові пакети для роботи з &lt;code&gt;Microsoft Identity&lt;/code&gt; та &lt;code&gt;EntityFramework Core&lt;/code&gt;. Їх можна додати командою &lt;code&gt;dotnet add package&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.AspNetCore.Identity.EntityFrameworkCore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.AspNetCore.Identity.UI&lt;/span&gt;&lt;span class="w"&gt;     
&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.EntityFrameworkCore.Design&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Npgsql.EntityFrameworkCore.PostgreSQL&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PackageReference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Microsoft.AspNetCore.Identity.EntityFrameworkCore'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'3.1.6'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PackageReference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Microsoft.AspNetCore.Identity.UI'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'3.1.6'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PackageReference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Microsoft.EntityFrameworkCore.Design'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'3.1.6'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PackageReference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Npgsql.EntityFrameworkCore.PostgreSQL'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'3.1.4'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер додамо клас &lt;code&gt;ApplicationDbContext&lt;/code&gt;, це вкрай важлива частина при роботі з EntityFramework і точка доступу до нашої бази даних. Цей клас повинен наслідуватись від &lt;code&gt;IdentityDbContext&lt;/code&gt;, тому що ми хочемо, щоб Microsoft Identity був прив'язаний до нашого контексту, а отже дані користувачів зберігались у створеній нами базі даних.&lt;/p&gt;

&lt;p&gt;Створіть новий файл &lt;code&gt;ApplicationDbContext.cs&lt;/code&gt; з таким вмістом:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Identity.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DotnetAppHeroku&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationDbContext&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IdentityDbContext&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ApplicationDbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbContextOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApplicationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Наразі EntityFramework не знає як працювати з конкретною базою даних, адже це універсальний фреймворк, що надає функції маппінгу об'єктів C# в записи бази даних. Тож нам необхідно використати відповідний драйвер. Для Postgres найбільш популярним є пакет &lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Npgsql/" rel="noopener noreferrer"&gt;Npgsql&lt;/a&gt;&lt;/strong&gt;. В &lt;a href="https://www.npgsql.org/efcore/" rel="noopener noreferrer"&gt;цьому дописі&lt;/a&gt; ви можете подивитись як підключити базу даних &lt;code&gt;PostgreSQL&lt;/code&gt; з &lt;code&gt;EntityFramework Core&lt;/code&gt; до вашої програми. Як ви бачите, для конфігурації драйверу необхідно передати рядок з'єднання у форматі &lt;code&gt;Host=my_host;Database=my_db;Username=my_user;Password=my_pw&lt;/code&gt;. Де взяти ці данні? Запитаємо конфігурацію нашої програми у Heroku.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heroku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet-app-heroku&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet-app-heroku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Vars&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;DATABASE_URL:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;postgres://zevxwvnofzdqgh:6fbfa85e483c547461d8dd1b1cdb5c3889ee11016ac278626056d317f20eb590&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;ec2-52-22-216-69.compute-1.amazonaws.com:5432/d67ogunajtekdt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Як бачите, Heroku створив змінну оточення &lt;code&gt;DATABASE_URL&lt;/code&gt;, але формат рядка з'єднання дещо відрізняється від того, що очікує драйвер &lt;code&gt;Npgsql&lt;/code&gt;. PostgreSQL використовує загальноприйнятий стандарт URL для підключення до БД. Цей формат описаний в &lt;a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING" rel="noopener noreferrer"&gt;документації&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://[user[:password]@][netloc][:port][,...][/dbname]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Нам необхідно додати трохи коду, щоб перетворити URL, який пропонує Heroku, в конфігурацію з'єднання, яку вимагає EntityFramework.&lt;/p&gt;

&lt;p&gt;Додайте файл &lt;code&gt;ConfigurationExtensions.cs&lt;/code&gt; з наступним вмістом:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DotnetAppHeroku&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigurationExtensions&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UriBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"Host=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; 
                   &lt;span class="s"&gt;$"Database=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;;"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; 
                   &lt;span class="s"&gt;$"Username=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; 
                   &lt;span class="s"&gt;$"Password=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; 
                    &lt;span class="s"&gt;"sslmode=Require;"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; 
                    &lt;span class="s"&gt;"Trust Server Certificate=true;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Два останні параметри &lt;code&gt;sslmode=Require&lt;/code&gt; та &lt;code&gt;Trust Server Certificate=true;&lt;/code&gt; необхідні, адже Heroku підтримує тільки з'єднання, що захищені через SSL.&lt;/p&gt;

&lt;p&gt;Маючи метод розширення, що повертає рядок для з'єднання, можемо налаштувати контекст бази даних та сервіси &lt;code&gt;Identity&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Змініть метод &lt;code&gt;ConfigureServices&lt;/code&gt; у файлі &lt;code&gt;Startup.cs&lt;/code&gt;, щоб він містив дану конфігурацію:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApplicationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseNpgsql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDefaultIdentity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IdentityUser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignIn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequireConfirmedAccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEntityFrameworkStores&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApplicationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Також додайте аутентифікацію в методі &lt;code&gt;Configure&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер підключення до бази даних налаштоване і ми можемо згенерувати таблиці, індекси та інші об'єкти БД, що потребує &lt;code&gt;Identity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;migrations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InitialMigration&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;started...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;succeeded.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Done.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;undo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ef migrations remove'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Застосуємо створену міграцію до бази даних:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;started...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;succeeded.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Done.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Після цього можемо переконатись, що база даних була успішно створена та містить всі необхідні таблиці. Для цього я підключився до бази даних через менеджер &lt;a href="https://www.heidisql.com/" rel="noopener noreferrer"&gt;HeidiSQL&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fdatabse-structure.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fdatabse-structure.jpg"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Налаштування інтерфейсу аутентифікації
&lt;/h2&gt;

&lt;p&gt;Нам залишилось змінити інтерфейс веб-програми, аби зробити можливою реєстрацію користувача і його наступний вхід:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Додати новий шаблон для сторінки реєстрації та входу.&lt;/li&gt;
&lt;li&gt;Підключити доданий шаблон до головної сторінки.&lt;/li&gt;
&lt;li&gt;Додати Identity UI до нашої програми. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Тож додамо в папку &lt;code&gt;Pages/Shared&lt;/code&gt; файл &lt;code&gt;_LoginPartial.cshtml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@using Microsoft.AspNetCore.Identity
@inject SignInManager&lt;span class="nt"&gt;&amp;lt;IdentityUser&amp;gt;&lt;/span&gt; SignInManager
@inject UserManager&lt;span class="nt"&gt;&amp;lt;IdentityUser&amp;gt;&lt;/span&gt; UserManager

&lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"navbar-nav"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
@if (SignInManager.IsSignedIn(User))
{
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link text-dark"&lt;/span&gt; &lt;span class="na"&gt;asp-area=&lt;/span&gt;&lt;span class="s"&gt;"Identity"&lt;/span&gt; &lt;span class="na"&gt;asp-page=&lt;/span&gt;&lt;span class="s"&gt;"/Account/Manage/Index"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Manage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello @User.Identity.Name!&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-inline"&lt;/span&gt; &lt;span class="na"&gt;asp-area=&lt;/span&gt;&lt;span class="s"&gt;"Identity"&lt;/span&gt; &lt;span class="na"&gt;asp-page=&lt;/span&gt;&lt;span class="s"&gt;"/Account/Logout"&lt;/span&gt; &lt;span class="na"&gt;asp-route-returnUrl=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Page("&lt;/span&gt;&lt;span class="err"&gt;/",&lt;/span&gt; &lt;span class="na"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;area = &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link btn btn-link text-dark"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
}
else
{
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link text-dark"&lt;/span&gt; &lt;span class="na"&gt;asp-area=&lt;/span&gt;&lt;span class="s"&gt;"Identity"&lt;/span&gt; &lt;span class="na"&gt;asp-page=&lt;/span&gt;&lt;span class="s"&gt;"/Account/Register"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Register&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link text-dark"&lt;/span&gt; &lt;span class="na"&gt;asp-area=&lt;/span&gt;&lt;span class="s"&gt;"Identity"&lt;/span&gt; &lt;span class="na"&gt;asp-page=&lt;/span&gt;&lt;span class="s"&gt;"/Account/Login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
}
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;У файлі &lt;code&gt;_Layout.cshtml&lt;/code&gt; знайдіть елемент &lt;code&gt;div&lt;/code&gt; з класом &lt;code&gt;"navbar-collapse"&lt;/code&gt; і додайте рядок відразу за ним:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;partial&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_LoginPartial"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Створіть папку &lt;code&gt;Areas\Identity\Pages\&lt;/code&gt; і в ній файл &lt;code&gt;_ViewStart.cshtml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Layout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/Pages/Shared/_Layout.cshtml"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Додайте до файлу імпорту &lt;code&gt;_ViewImports.cshtml&lt;/code&gt; простір імен для &lt;code&gt;Microsoft Identity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер можна виконати &lt;code&gt;build&lt;/code&gt; проєкту і переконатись, що все працює. Зробіть коміт і пуш у репозиторій Heroku. Ваша програма буде автоматично розгорнута (адже ми сконфігурували це в першій статті) і через деякий час доступна за адресою &lt;code&gt;http://dotnet-app-heroku.herokuapp.com/&lt;/code&gt;. Спробуйте зареєструвати нового користувача і потім увійти від його імені:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fworking-app.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmartyniuk.dev%2Fuk%2Fassets%2Fimg%2Fposts%2F2020-07-28-add-database-to-app-in-heroku%2Fworking-app.jpg"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Статистика використання БД
&lt;/h2&gt;

&lt;p&gt;Давайте переглянемо деяку статистику по базі даних, що дасть нам уявлення про те, як вона використовується і які обмеження порушені:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heroku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pg:info&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Plan:&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nx"&gt;Hobby-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Status:&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;Available&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Connections:&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nx"&gt;1/20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version:&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;12.3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Created:&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="nx"&gt;2020-07-28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;16:28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UTC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;Data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Size:&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nx"&gt;8.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Tables:&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Rows:&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nx"&gt;2/10000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;In&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compliance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Fork/Follow:&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nx"&gt;Unsupported&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Rollback:&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nx"&gt;Unsupported&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Continuous&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Protection:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Off&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-on:&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;postgresql-horizontal-81849&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Як бачимо, на даний момент є одне з'єднання і використано 8.5 MB сховища. Також створено 8 таблиць і 2 рядки: перший рядок для новоствореного користувача і другий - це запис в таблиці з міграціями EntityFramework Core.&lt;/p&gt;

&lt;h2&gt;
  
  
  Висновок
&lt;/h2&gt;

&lt;p&gt;Ми побачили, як:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Створити базу даних &lt;code&gt;PostgreSQL&lt;/code&gt; в &lt;code&gt;Heroku&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Налаштувати &lt;code&gt;EntityFramework Core&lt;/code&gt; та &lt;code&gt;Microsoft Identity&lt;/code&gt; для роботи з базою даних.&lt;/li&gt;
&lt;li&gt;Додати елементи інтерфейсу для реєстрації та входу користувача.&lt;/li&gt;
&lt;li&gt;Переглянути статистику використання бази даних.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/alexmartyniuk/blog-dotnet-app-heroku" rel="noopener noreferrer"&gt;Репозиторій на Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Більш детальну інформацію про Heroku Postgres ви можете знайти в &lt;a href="https://devcenter.heroku.com/articles/heroku-postgresql#provisioning-heroku-postgres" rel="noopener noreferrer"&gt;офіційній документації&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Оригінальна стаття на &lt;a href="https://martyniuk.dev/uk/posts/%D0%B1%D0%B5%D0%B7%D0%BA%D0%BE%D1%88%D1%82%D0%BE%D0%B2%D0%BD%D0%B0-%D0%B1%D0%B4-%D0%B4%D0%BB%D1%8F-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%B8-aspnet-core/" rel="noopener noreferrer"&gt;моєму сайті&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>postgres</category>
      <category>heroku</category>
      <category>identity</category>
    </item>
    <item>
      <title>Зустрічайте C# 9.0</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Sun, 14 Jun 2020 13:51:24 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/c-9-0-5ap7</link>
      <guid>https://dev.to/olesmartyniuk/c-9-0-5ap7</guid>
      <description>&lt;p&gt;Це переклад статті &lt;a href="https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/"&gt;"Welcome to C# 9.0"&lt;/a&gt; Медса Тоерсена - працівника Microsoft і головного дизайнера мови C#.&lt;/p&gt;




&lt;p&gt;C# 9.0 набуває форм і я хочу поділитись нашим баченням найбільш важливих можливостей, які ми додаємо в наступну версію мови.&lt;/p&gt;

&lt;p&gt;З кожною новою версією ми прагнемо зробити мову більш ясною і простою для загальних сценаріїв використання і C# 9.0 не є винятком. Цього разу нашим фокусом є забезпечення лаконічності в представленні даних та підтримка механізмів їх незмінності.&lt;/p&gt;

&lt;p&gt;Ну що ж, поїхали!&lt;/p&gt;

&lt;h2&gt;
  
  
  Властивості з ініціалізацією
&lt;/h2&gt;

&lt;p&gt;Ініціалізатори об'єктів надзвичайно корисні: вони дають програмісту дуже гнучку і, водночас, зрозумілу форму створення об'єктів. Вони особливо зручні для створення вкладених об'єктів, коли ціла ієрархія створюється однією командою. Ось приклад:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Scott"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hunter"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ініціалізатори об'єктів також звільняють автора класу від написання шаблонного коду конструкторів. Все, що потрібно, - лише створити певні властивості!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проте сьогодні є одне велике обмеження: щоб ініціалізатори працювали, властивості повинні бути &lt;em&gt;змінюваними&lt;/em&gt; (mutable). Ініціалізатори працюють завдяки виклику конструктора (без параметрів в даному випадку) і наступному присвоєнню значень через виклик сетерів властивостей.&lt;/p&gt;

&lt;p&gt;Властивості з ініціалізацією виправлять це! Вони визначають &lt;code&gt;init&lt;/code&gt; аксесор який дуже схожий на &lt;code&gt;set&lt;/code&gt; аксесор, але може викликатись лише під час ініціалізації об'єкту:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;З таким підходом код, написаний вище, все ще коректний, але всі наступні присвоєння значень властивостям &lt;code&gt;FirstName&lt;/code&gt; і &lt;code&gt;LastName&lt;/code&gt; викличуть помилку.&lt;/p&gt;

&lt;h3&gt;
  
  
  Init аксесори та поля лише для читання
&lt;/h3&gt;

&lt;p&gt;Так як &lt;code&gt;init&lt;/code&gt; аксесори можуть викликатись лише під час ініціалізації, їм дозволено змінювати поля для читання того ж класу, точно так, як ви зараз можете зробити це у конструкторі.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Класи даних (record)
&lt;/h2&gt;

&lt;p&gt;Властивості з ініціалізацією чудово підходять, якщо ви хочете зробити певну властивість об'єкту незмінною. Якщо ж ви хочете зробити незмінним весь об'єкт, щоб він поводив себе як екземпляр типу-значення, вам необхідно визначити його як &lt;em&gt;клас даних&lt;/em&gt; (record):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ключове слово &lt;code&gt;data&lt;/code&gt; у визначенні позначає його як клас даних. Це додає йому певної поведінки характерної для типу-значення, яку ми розглянемо далі. Загалом, класи даних краще розглядати як "значення" (дані), а не як об'єкти. Вони не створені, щоб мати змінюваний стан. Натомість, ви виражаєте зміни у часі, створюючи нові екземпляри класу даних, що відображують новий стан. Класи даних визначаються не унікальністю посилання, а тотожністю вмісту.&lt;/p&gt;

&lt;h3&gt;
  
  
  Вираз &lt;code&gt;with&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;При роботі з незмінними даними, загальний підхід полягає у створенні копії для відображення нового стану об'єкту. Для прикладу, якщо наша особа захоче змінити своє прізвище, ми реалізуємо це через створення нового об'єкту, що буде копією старого окрім зміненого прізвища. Цю техніку часто називають &lt;em&gt;неруйнівною мутацією&lt;/em&gt;. Замість того, щоб змінювати особу &lt;em&gt;з часом&lt;/em&gt; клас даних відображує стан особи в &lt;em&gt;конкретний момент часу&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;Щоб програмувати в такому стилі було легше, класи даних підтримують новий тип виразу - &lt;code&gt;with&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;otherPerson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hanselman"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With-вирази використовують синтаксис ініціалізаторів, щоб визначити, чим будуть відрізнятись новий і старий об'єкти. Ви можете задати відразу декілька властивостей.&lt;/p&gt;

&lt;p&gt;Клас даних неявно включає захищений "конструктор копіювання" - це конструктор, який бере об'єкт класу даних, що існує, і копіює його поля одне за одним в новий об'єкт:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* копіює всі поля */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// автозгенерований&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Вираз &lt;code&gt;with&lt;/code&gt; викликає конструктор копіювання і потім застосовує ініціалізатор для перевизначених властивостей, але вже до проініціалізованих раніше даних.&lt;/p&gt;

&lt;p&gt;Якщо вас не влаштовує згенерований конструктор копіювання, ви можете визначити свій власний і він так само буде використовуватись виразом &lt;code&gt;with&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Порівняння за значенням
&lt;/h3&gt;

&lt;p&gt;Всі об'єкти наслідують віртуальний метод &lt;code&gt;Equals(object)&lt;/code&gt; від класу &lt;code&gt;object&lt;/code&gt;. Він використовується статичним методом &lt;code&gt;Object.Equals(object, object)&lt;/code&gt; коли обидва параметри не дорівнюють &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Структури перевизначають його, щоб отримати "порівняння за значенням". Це коли поля структури порівнюються рекурсивно через виклик &lt;code&gt;Equals&lt;/code&gt;. Класи даних роблять так само.&lt;/p&gt;

&lt;p&gt;Це означає те, що згідно з їхньою "значеністю", два об'єкти класу даних можуть бути рівними, будучи &lt;em&gt;різними&lt;/em&gt; екземплярами одного типу. Для прикладу, якщо ми повернемо назад прізвище у раніше зміненої особи:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;originalPerson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;otherPerson&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hunter"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер ми мали б &lt;code&gt;ReferenceEquals(person, originalPerson)&lt;/code&gt; = false (це різні екземпляри), але &lt;code&gt;Equals(person, originalPerson)&lt;/code&gt; = true (вони містять однакові дані).&lt;/p&gt;

&lt;p&gt;Якщо вам не підходить порівняння по полях, що визначається початково, ви можете написати своє. Але треба бути обережним і розуміти, як працює порівняння за значеннями в структурах, особливо якщо є наслідування (до якого ми ще повернемося нижче).   &lt;/p&gt;

&lt;p&gt;Поряд з перевизначенням &lt;code&gt;Equals&lt;/code&gt; перевизначається також &lt;code&gt;GetHashCode()&lt;/code&gt;, бо вони працюють у парі.&lt;/p&gt;

&lt;h3&gt;
  
  
  Поля класів даних
&lt;/h3&gt;

&lt;p&gt;Класи даних задумувались незмінюваними і такими, що містять лише публічні властивості з ініціалізаторами. Класи даних можуть змінюватись в не деструктивний спосіб завдяки &lt;code&gt;with&lt;/code&gt;-виразам. Для того, щоб спростити визначення класів даних для цього поширеного випадку, синтаксис класу даних змінює семантику, що несе &lt;code&gt;string FirstName&lt;/code&gt;. Замість неявного приватного поля, як це було б у визначенні класу чи структури, в синтаксисі класу даних це означає публічну автовластивість з ініціалізатором! Таким чином, визначення:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Означає в точності те ж, що ми мали раніше:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ми вважаємо, це дозволяє зробити визначення класу даних чистим і красивим. Якщо вам дійсно потрібне приватне поле, ви завжди можете додати модифікатор &lt;code&gt;private&lt;/code&gt; явно:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Позиційні класи даних
&lt;/h3&gt;

&lt;p&gt;Інколи зручно використовувати більш позиційний підхід до класів даних, при якому дані передаються через аргументи конструктора і можуть бути отримані назад завдяки позиційному деконструюванню.&lt;/p&gt;

&lt;p&gt;Абсолютно допустимим є визначення власного конструктора і деконструктора класу даних:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Deconstruct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Але існує набагато коротший синтаксис для вираження того самого (зверніть увагу на регістр імен параметрів):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Цей запис визначає публічні автовластивості і конструктор з деконструктором, тож ви можете написати:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Scott"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hunter"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// позиціне конструювання&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                        &lt;span class="c1"&gt;// позиціне деконструювання&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Якщо вам не подобається згенерована автовластивіть, ви можете натомість визначити свою власну з тим же іменем, і згенеровані конструктор та деконструктор будуть її використовувати.&lt;/p&gt;

&lt;h3&gt;
  
  
  Класи даних і змінюваний стан
&lt;/h3&gt;

&lt;p&gt;Семантика значення не дуже добре поєднується зі змінюваним станом. Уявіть, ми помістили об'єкт класу даних в словник. Його подальший пошук залежить від &lt;code&gt;Equals&lt;/code&gt; та (інколи) &lt;code&gt;GethashCode&lt;/code&gt;. Але, якщо клас даних змінює свій стан, він також змінює свою еквівалентність! Ми можемо не знайти його знову! В реалізації хеш таблиці це може пошкодити структуру даних, бо розміщення об'єкту грунтується на хеш коді, який він має в момент запису у таблицю.&lt;/p&gt;

&lt;p&gt;Напевно, є допустимі приклади використання змінюваного стану класів даних, зокрема для кешування. Але щоб перевизначити поведінку так, щоб ігнорувати цей стан, доведеться докласти &lt;em&gt;значних&lt;/em&gt; зусиль.&lt;/p&gt;

&lt;h3&gt;
  
  
  With-вираз та наслідування
&lt;/h3&gt;

&lt;p&gt;Порівняння за значенням та недеструктивна мутація значно ускладнюються, коли поєднуються з наслідуванням. Давайте додамо похідний клас даних &lt;code&gt;Student&lt;/code&gt; до раніше розглянутого прикладу:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Student&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Почнемо наш приклад зі створення екземпляру &lt;code&gt;Student&lt;/code&gt;, але збережемо його у змінній типу &lt;code&gt;Person&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Student&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Scott"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hunter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetNewId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;otherPerson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hanselman"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В останньому рядку з &lt;code&gt;with&lt;/code&gt;-виразом компілятор не знає, що &lt;code&gt;person&lt;/code&gt; фактично містить екземпляр &lt;code&gt;Student&lt;/code&gt;. Тим не менш, новий екземпляр &lt;code&gt;otherPerson&lt;/code&gt; не був би коректною копією, якби він &lt;em&gt;не був екземпляром&lt;/em&gt; &lt;code&gt;Student&lt;/code&gt; і не містив той самий &lt;code&gt;ID&lt;/code&gt;, що і оригінальний об'єкт. &lt;/p&gt;

&lt;p&gt;C# робить це за нас. Класи даних містять прихований віртуальний метод, якому доручено клонування &lt;em&gt;цілого&lt;/em&gt; об'єкту. Кожен похідний тип класу даних перевизначає цей метод і викликає конструктор копіювання для цього типу. Цей конструктор викликає аналогічний конструктор базового типу. &lt;code&gt;With&lt;/code&gt;-вираз просто викликає цей прихований метод клонування і застосовує ініціалізатор об'єкту до результату.&lt;/p&gt;

&lt;h3&gt;
  
  
  Порівняння за значенням і наслідування
&lt;/h3&gt;

&lt;p&gt;Подібно до реалізації &lt;code&gt;with&lt;/code&gt;-виразів, порівняння за значенням також повинно бути "віртуальним" в тому сенсі, що для порівняння двох екземплярів типу &lt;code&gt;Student&lt;/code&gt; повинні бути порівняні всі поля типу &lt;code&gt;Student&lt;/code&gt;, навіть якщо тип посилання на момент порівняння - це базовий тип, наприклад, &lt;code&gt;Person&lt;/code&gt;. Цього легко досягти, перевизначивши метод &lt;code&gt;Equals&lt;/code&gt;, що наразі вже є віртуальним.&lt;/p&gt;

&lt;p&gt;Проте, є ще одна проблема з еквівалентністю. Що, як ви порівнюєте два &lt;em&gt;різних&lt;/em&gt; підтипи базового типу &lt;code&gt;Person&lt;/code&gt;? Ми не можемо дозволити вибирати метод &lt;code&gt;Equal&lt;/code&gt; якого типу використовувати: еквівалентність повинна бути симетричною. Тож результат не повинен залежати від порядку об'єктів. Іншими словами, вони повинні самі &lt;em&gt;узгодити&lt;/em&gt;, чию еквівалентність застосовувати.    &lt;/p&gt;

&lt;p&gt;Цей приклад ілюструє проблему:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Scott"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hunter"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Student&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Scott"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hunter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetNewId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Чи рівні ці два об'єкти? &lt;code&gt;person1&lt;/code&gt; може думати, що так, адже &lt;code&gt;person2&lt;/code&gt; має всі поля типу &lt;code&gt;Person&lt;/code&gt; з тими ж значеннями. Але &lt;code&gt;person2&lt;/code&gt; з цим не погодиться! Ми повинні бути впевненими, що вони обоє розуміють, що вони все ж різні об'єкти. &lt;/p&gt;

&lt;p&gt;І знову, C# подбав про це. Спосіб, в який це реалізовано: класи даних мають віртуальну захищену властивість &lt;code&gt;EqualityContract&lt;/code&gt;. Кожен похідний клас даних перевизначає її і для того, щоб два класи даних були однаковими, вони повинні мати один і той самий &lt;code&gt;EqualityContract&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Програми найвищого рівня
&lt;/h2&gt;

&lt;p&gt;Написання простої програми на C# вимагає значної кількості шаблонного коду:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Це не тільки важко сприймати тим, хто тільки починає вивчати мову, а ще й захаращує код та додає зайві рівні відступів.&lt;/p&gt;

&lt;p&gt;Натомість, в C# 9.0 ви можете просто писати вашу програму на найвищому рівні:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Будь-які вирази дозволені. Програма може початися відразу після списку &lt;code&gt;using&lt;/code&gt; і перед визначенням будь якого типу чи простору імен. Але ви можете так робити тільки в одному файлі, точно так як зараз ви можете мати лише один метод &lt;code&gt;Main&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Якщо ви хочете повернути код статусу, ви можете це зробити. Якщо ви хочете очікувати задачу з &lt;code&gt;await&lt;/code&gt;, ви можете це зробити. І якщо ви хочете отримати доступ до аргументів командного рядку, вони доступні як "магічний" параметр &lt;code&gt;args&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Локальні функції - це також вирази і вони так само дозволені на найвищому рівні. Але, у разі виклику їх поза областю програми найвищого рівня, буде згенеровано помилку.&lt;/p&gt;

&lt;h2&gt;
  
  
  Вдосконалене співставлення за шаблоном
&lt;/h2&gt;

&lt;p&gt;Декілька нових типів шаблонів порівняння були додані в C# 9.0. Давайте подивимось на них в контексті цього фрагменту коду з &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/pattern-matching"&gt;навчального посібника&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateToll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;vehicle&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="p"&gt;...&lt;/span&gt;

        &lt;span class="n"&gt;DeliveryTruck&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrossWeightClass&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;5.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DeliveryTruck&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrossWeightClass&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DeliveryTruck&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not a known vehicle type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Спрощений шаблон типу
&lt;/h3&gt;

&lt;p&gt;Наразі, шаблон типу вимагає визначення змінної, навіть якщо ця змінна ігнорується, як у випадку з &lt;code&gt;_&lt;/code&gt;. У прикладі вище це &lt;code&gt;DeliveryTruck _&lt;/code&gt;. Але тепер ви можете просто написати тип:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;DeliveryTruck&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Шаблони порівняння
&lt;/h3&gt;

&lt;p&gt;C# 9.0 вводить шаблони на основі операторів порівняння &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt; і т.ін. Тож тепер ви можете написати чатину з &lt;code&gt;DeliveryTruck&lt;/code&gt; з наведеного вище шаблону як вкладений switch вираз:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;DeliveryTruck&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrossWeightClass&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;5.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тут &lt;code&gt;&amp;gt; 5000&lt;/code&gt; і &lt;code&gt;&amp;lt; 3000&lt;/code&gt; - це шаблони порівняння.&lt;/p&gt;

&lt;h3&gt;
  
  
  Логічні шаблони
&lt;/h3&gt;

&lt;p&gt;Нарешті ви можете поєднувати шаблони з логічними операторами &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt; і &lt;code&gt;not&lt;/code&gt;. Вони записані словами, щоб уникнути плутанини з операторами у виразах. Для прикладу, випадок з вкладеним switch виразом вище можна переписати розташувавши діапазони по зростанню, як наведено нижче:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;DeliveryTruck&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrossWeightClass&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;5.00&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Середній вираз тут використовує &lt;code&gt;and&lt;/code&gt;, щоб поєднати два шаблони порівняння в єдиний шаблон, що відображує діапазон. &lt;/p&gt;

&lt;p&gt;Шаблон &lt;code&gt;not&lt;/code&gt; може використовуватись спільно з констатним шаблоном &lt;code&gt;null&lt;/code&gt;, як &lt;code&gt;not null&lt;/code&gt;. Для прикладу, ми можемо розділити обробку невідомого випадку в залежності від того, чи дорівнює він &lt;code&gt;null&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Not a known vehicle type: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Також &lt;code&gt;not&lt;/code&gt; може бути корисним з оператором if, умова якого містить вираз &lt;code&gt;is&lt;/code&gt; замість потворних подвійних дужок:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ви можете просто написати&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Вдосконалене приведення до цільового типу
&lt;/h2&gt;

&lt;p&gt;"Приведенням до цільового типу" ми називаємо ситуацію, коли вираз отримує свій тип з контексту, в якому він використовується. Для прикладу &lt;code&gt;null&lt;/code&gt; чи лямбда-вирази завжди використовують приведення до цільового типу.&lt;/p&gt;

&lt;p&gt;В C# 9.0 деякі вирази, які до цього не використовували приведення до цільового типу, отримали можливість визначати тип з контексту.&lt;/p&gt;

&lt;h3&gt;
  
  
  Приведення у виразі &lt;code&gt;new&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Вираз &lt;code&gt;new&lt;/code&gt; у C# завжди вимагав вказування типу (за винятком неявно типізованих масивів). Тепер ви можете пропустити тип, якщо змінна, якій присвоюється вираз, вже його має.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Приведення у виразах &lt;code&gt;??&lt;/code&gt; та &lt;code&gt;?:&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Інколи умовні вирази &lt;code&gt;??&lt;/code&gt; та &lt;code&gt;?:&lt;/code&gt; не мають явного спільного типу між різними гілками виконання. Такі випадки зараз не компілюються, але C# 9.0 буде дозволяти їх, якщо існує цільовий тип, до якого обидва результати можуть бути приведені.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;student&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// спільний базовий тип&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// значений nullable тип&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Коваріантні результати
&lt;/h2&gt;

&lt;p&gt;Інколи корисно якось виразити те, що перевизначений метод похідного класу повертає більш конкретний тип, ніж визначено у базовому класі. C# 9.0 дозволяє таке:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="n"&gt;Food&lt;/span&gt; &lt;span class="nf"&gt;GetFood&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tiger&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Animal&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;Meat&lt;/span&gt; &lt;span class="nf"&gt;GetFood&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  І навіть більше…
&lt;/h2&gt;

&lt;p&gt;Найкраще місце, щоб ознайомитися з повним переліком майбутніх можливостей в C# 9.0 і слідкувати за їх реалізацією - це сторінка &lt;a href="https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md"&gt;Статус реалізації нових можливостей мови&lt;/a&gt; в репозиторії Roslyn (C#/VB Compiler) на GitHub.&lt;/p&gt;

&lt;p&gt;Приємного кодування!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://martyniuk.dev/uk/posts/%D0%B7%D1%83%D1%81%D1%82%D1%80%D1%96%D1%87%D0%B0%D0%B9%D1%82%D0%B5-csharp-9/"&gt;Оригінал перекладу&lt;/a&gt; на моєму сайті.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>translation</category>
      <category>ukrainian</category>
    </item>
    <item>
      <title>Обмеження одночасних потоків в C#</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Fri, 29 May 2020 15:29:58 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/c-2kmd</link>
      <guid>https://dev.to/olesmartyniuk/c-2kmd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Це переклад допису &lt;a href="https://markheath.net/post/constraining-concurrent-threads-csharp"&gt;Constraining Concurrent Threads in C#&lt;/a&gt; Марка Хіта - Microsoft MVP, Software Architect в NICE Systems і автора кількох бібліотек з відкритим кодом.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Припустимо, в C# ми маємо виконати певну кількість задач, які наразі виконуються послідовно і які ми хочемо прискорити, запустивши їх паралельно. Для прикладу, уявіть, що ми завантажуємо купу веб сторінок:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="s"&gt;"https://github.com/naudio/NAudio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s"&gt;"https://twitter.com/mark_heath"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s"&gt;"https://github.com/markheath/azure-functions-links"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://pluralsight.com/authors/mark-heath"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://github.com/markheath/advent-of-code-js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"http://stackoverflow.com/users/7532/mark-heath"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://mvp.microsoft.com/en-us/mvp/Mark%20%20Heath-5002551"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://github.com/markheath/func-todo-backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://github.com/markheath/typescript-tetris"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"retrieved &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters from &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Щоб запустити їх паралельно, ми могли б запустити для кожного завантаження &lt;code&gt;Task&lt;/code&gt; через &lt;code&gt;Task.Run&lt;/code&gt; і очікувати, поки вони всі завершаться. Але як бути, якщо ми хочемо &lt;strong&gt;обмежити кількість одночасних завантажень&lt;/strong&gt;? Припустимо, ми хочемо обмежити їх чотирма.&lt;/p&gt;

&lt;p&gt;В цьому спрощеному прикладі точна кількість задач не має принципового значення, але неважко уявити ситуацію, в якій ви захочете обмежити кількість одночасних запитів до певного сервісу.&lt;/p&gt;

&lt;p&gt;В цьому дописі я хочу розглянути чотири різні способи вирішення цієї проблеми.&lt;/p&gt;

&lt;h2&gt;
  
  
  Спосіб 1 - ConcurrentQueue
&lt;/h2&gt;

&lt;p&gt;Перший спосіб був для мене звичним підходом протягом багатьох років. Основна ідея - поставити всі завдання в чергу і обробляти їх декількома потоками, які читають з черги послідовно. Це непоганий і доволі простий підхід, який, проте, вимагає блокування черги, так як вона буде використовуватись декількома потоками одночасно. В цьому прикладі я використовую &lt;code&gt;ConcurrentQueue&lt;/code&gt;, щоб зробити це потокобезпечно. &lt;/p&gt;

&lt;p&gt;Ми заповнюємо чергу адресами сторінок для завантаження і запускаємо одну задачу для кожного потоку, які в циклі намагаються читати з черги. Потоки закінчуються, коли не лишається елементів в черзі. Ми створюємо список таких задач і потім використовуємо &lt;code&gt;Task.WhenAll&lt;/code&gt;, щоб дочекатись їх завершення. Вони всі завершаться, коли завершиться останнє завантаження.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;maxThreads&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConcurrentQueue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maxThreads&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryDequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"retrieved &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters from &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Мені досі подобається цей підхід, адже він концептуально простий. Проте, можуть бути певні незручності, якщо продовжувати додавати нові завдання після того, як ми почали їх обробку. Причина в тому, що потоки, що читають з черги, можуть завершитись зарано. &lt;/p&gt;

&lt;h2&gt;
  
  
  Спосіб 2 - SemaphoreSlim
&lt;/h2&gt;

&lt;p&gt;Інший підхід (на який мене надихнула ця &lt;a href="https://stackoverflow.com/questions/10806951/how-to-limit-the-amount-of-concurrent-async-i-o-operations/10810730#10810730"&gt;відповідь зі StackOverflow&lt;/a&gt;) - це скористатись &lt;code&gt;SemaphoreSlim&lt;/code&gt; з &lt;code&gt;initialCount&lt;/code&gt; рівним максимальному числу потоків. Потім ми використовуємо &lt;code&gt;WaitAsync&lt;/code&gt; для очікування моменту, коли можна буде запустити наступну задачу. Таким чином, ми відразу стартуємо чотири задачі, але далі повинні дочекатись, поки якась з них завершиться перед тим, як виконається &lt;code&gt;WaitAsync&lt;/code&gt; і з'явиться можливість додати наступну.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allTasks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;throttler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SemaphoreSlim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;maxThreads&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;throttler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;allTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"retrieved &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters from &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;finally&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;throttler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allTasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Цей код трішки наочніший, ніж підхід з &lt;code&gt;ConcurrentQueue&lt;/code&gt;. Також він призводить до ситуації, коли ми отримаємо список з потенційно великою кількістю вже завершених задач. Але цей підхід має перевагу, якщо під час виконання задач ви генеруєте нові.&lt;/p&gt;

&lt;p&gt;Для прикладу, для передачі великого файлу до Azure Blob Storage ви можете послідовно читати порціями в 1MB, але бажаєте завантажувати їх по чотири одночасно. Ви не хочете читати всі порції файлу до моменту поки їх не потрібно буде відправити, оскільки це потребує багато часу і пам'яті. З даним підходом ми можемо створювати задачу саме в той момент, коли потік звільнився і готовий завантажувати наступну порцію. Це набагато ефективніше.&lt;/p&gt;

&lt;h2&gt;
  
  
  Спосіб 3 - Parallel.ForEach
&lt;/h2&gt;

&lt;p&gt;Метод &lt;a href="https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx"&gt;&lt;code&gt;Parallel.ForEach&lt;/code&gt;&lt;/a&gt;, на перший погляд, ідеальне вирішення цієї проблеми. Ви можете просто вказати &lt;code&gt;MaxDegreeOfParallelism&lt;/code&gt; і визначити &lt;code&gt;Action&lt;/code&gt;, що буде виконуватись для кожного елементу у вашому &lt;code&gt;IEnumerable&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ParallelOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;MaxDegreeOfParallelism&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxThreads&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"retrieved &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters from &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Виглядає гарно і просто, чи не так? Проте, тут є приховані граблі. Так як &lt;code&gt;Parallel.ForEach&lt;/code&gt; приймає &lt;code&gt;Action&lt;/code&gt;, а не &lt;code&gt;Func&amp;lt;T&amp;gt;&lt;/code&gt;, його можна використовувати лише зі синхронними функціями. Ви могли помітити, що ми викрутились, додавши &lt;code&gt;.Result&lt;/code&gt; після &lt;code&gt;GetStringAsync&lt;/code&gt;, але це небезпечний прийом, який використовувати не рекомендується.&lt;/p&gt;

&lt;p&gt;Тож, на жаль, цей підхід можна задіяти, якщо у вас є синхронні методи, які необхідно виконати паралельно. Існує Nuget пакет, що реалізує &lt;a href="https://www.nuget.org/packages/AsyncEnumerator/1.1.0"&gt;асинхронну версію Parallel.ForEach&lt;/a&gt;, тож ви можете спробувати його, якщо бажаєте отримати щось на кшталт:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;uris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParallelForEachAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"retrieved &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters from &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;maxDegreeOfParalellism&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;maxThreads&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Спосіб 4 - політика "Перемичка" бібліотеки Polly
&lt;/h2&gt;

&lt;p&gt;Останій варіант - скористатись &lt;a href="https://github.com/App-vNext/Polly/wiki/Bulkhead"&gt;політикою ізоляції Перемичка (Bulkhead)&lt;/a&gt; бібліотеки &lt;a href="https://github.com/App-vNext/Polly"&gt;Polly&lt;/a&gt;. Ця політика обмежує кількість одночасних викликів і, &lt;em&gt;за бажанням&lt;/em&gt;, дозволяє ставити в чергу виклики, що потрапили під обмеження.&lt;/p&gt;

&lt;p&gt;Нижче ми налаштовуємо політику перемички з обмеженням на число одночасних викликів та без обмеження на кількість задач, що очікують свого запуску в черзі. Далі ми просто викликаємо метод &lt;code&gt;ExecuteAsync&lt;/code&gt; в циклі, дозволяючи або негайно стартувати задачу, або поставити її в чергу, якщо задач забагато.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bulkhead&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BulkheadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxThreads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Int32&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bulkhead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"retrieved &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters from &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Аналогічно до інших згаданих способів ми зберігаємо задачі до списку і використовуємо &lt;code&gt;Task.WhenAll&lt;/code&gt; для очікування їх завершення. Варто зазначити, що даний шаблон насправді створений для ситуацій, в яких одночасні завдання генеруються декількома потоками (для прикладу, з контролерів ASP.NET). Вони можуть просто використовувати спільну політику перемички і ви лише запускаєте задачу з &lt;code&gt;await bulkhead.ExecuteAsync(...)&lt;/code&gt;. Тож цей підхід дуже простий і добре підходить для тих ситуацій, для яких він був спроєктований. &lt;/p&gt;

&lt;h2&gt;
  
  
  Висновок
&lt;/h2&gt;

&lt;p&gt;Паралелізація може значно прискорити виконання вашої програми. Але, якщо вона використовується неправильно, то може сама створити більше проблем, аніж розв'язує. Ці шаблони дозволяють вам використовувати обмежену кількість потоків для обробки групи задач. Єдине, з чим необхідно визначитись, - це спосіб, у який завдання створюються. Вони наявні з самого початку, чи створюються в процесі, в момент, коли вже відбувається обробка попередніх завдань? Також питання: ви генеруєте ці задачі послідовно з одного потоку чи декілька потоків мають можливість додавати задачі?&lt;/p&gt;

&lt;p&gt;Звичайно, я впевнений, що існують інші красиві способи розв'язання цієї проблеми, тож дайте знати в коментарях, який ваш улюблений.&lt;/p&gt;

&lt;h2&gt;
  
  
  Коментар Стівена Клірі
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Від перекладача:&lt;/p&gt;

&lt;p&gt;В обговорення цього допису завітав &lt;a href="https://blog.stephencleary.com/"&gt;Стівен Клірі&lt;/a&gt; - автор відомої книги &lt;a href="http://shop.oreilly.com/product/0636920266624.do?cmp=af-code-books-video-product_cj_0636920266624_7489747"&gt;Concurrency in C# Cookbook&lt;/a&gt;. Я думаю, його коментар варто перекласти також.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Я думаю, важливо розрізняти синхронну та асинхронну конкурентність. Синхронна конкурентність (паралелізм) це використання декількох потоків, і це правильний вибір, якщо ви маєте код, що інтенсивно використовує центральний процесор (CPU-bound). Асинхронна конкурентність - це форма виконання, що не вимагає додаткових потоків, і це правильний вибір, коли ви маєте код, що працює з введенням-виведенням (I/O-bound).&lt;/p&gt;

&lt;p&gt;Наведений приклад (завантаження веб сторінок) - це введення-виведення, а отже, тут краще підходить асинхронна конкурентність. Ось чому способи з &lt;code&gt;Parallel&lt;/code&gt; / &lt;code&gt;ConcurrentQueue&lt;/code&gt; / &lt;code&gt;BlockingCollection&lt;/code&gt; врешті виглядають незграбно: блокування потоків і т.ін. Вони справді незамінні у світі синхронної конкуренції і з ними, звісно, можна ров'язати дану проблему, але цей розв'язок буде менш ефективним.&lt;/p&gt;

&lt;p&gt;Для завантаження більше підходять методи асинхронної конкурентності. Вони включають використання &lt;code&gt;SemaphoreSlim&lt;/code&gt; з &lt;code&gt;Task.WhenAll&lt;/code&gt; (але без необов'язкового &lt;code&gt;Task.Run&lt;/code&gt;), і &lt;code&gt;TPL ActionBlock / BufferBlock&lt;/code&gt; (працюють як асинхронна &lt;code&gt;ConcurrentQueue&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Наприклад, підхід з &lt;code&gt;SempahoreSlim&lt;/code&gt; може бути спрощений до такого:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;throttler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SemaphoreSlim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;maxThreads&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;throttler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"retrieved &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters from &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;throttler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;І його навіть можна спростити ще більше, якщо визначити метод розширення &lt;code&gt;LockAsync&lt;/code&gt; для &lt;code&gt;SemaphoreSlim&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="http://www.martyniuk.info/posts/constraining-concurrent-threads-in-csharp/"&gt;Оригінальний допис на моєму сайті&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>concurrency</category>
      <category>parallelism</category>
      <category>translation</category>
    </item>
    <item>
      <title>Безкоштовна публікація ASP.NET Core програми</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Fri, 22 May 2020 19:03:47 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/net-core-webapp-14p6</link>
      <guid>https://dev.to/olesmartyniuk/net-core-webapp-14p6</guid>
      <description>&lt;p&gt;В багатьох програмістів є власні проєкти на .NET Core, які в певний момент хочеться показати іншим. Бажано, щоб розгортання відбувалось просто і швидко, а хостинг був безкоштовним. В цій замітці я розкажу, як розгортати проєкти на .NET Core в Heroku з безперервною доставкою, не вкладаючи в це ні копійки.&lt;/p&gt;

&lt;h2&gt;
  
  
  Передумови
&lt;/h2&gt;

&lt;p&gt;Для роботи нам потрібні будуть &lt;strong&gt;.NET Core SDK&lt;/strong&gt; (у мене встановлено версію 3.1), &lt;strong&gt;Git&lt;/strong&gt; для роботи з репозиторієм та &lt;strong&gt;Heroku CLI&lt;/strong&gt; для роботи з Heroku. Сподіваюсь, ви вже маєте &lt;a href="https://dotnet.microsoft.com/download"&gt;.NET Core&lt;/a&gt; та &lt;a href="https://git-scm.com/"&gt;Git&lt;/a&gt;. Як встановити Heroku CLI я розкажу далі.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heroku та безкоштовний dyno
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="http://heroku.com"&gt;Heroku&lt;/a&gt;&lt;/strong&gt; - хмарний провайдер, що дозволяє запускати веб програми і сервіси написані на мовах Java, Node.JS, Scala, Python, PHP, Ruby, Go та Clojure. Heroku була однією з перших хмарних платформ. В її основі лежить система віртуалізації побудована на ізольованих Linux контейнерах, які в Heroku називаються &lt;strong&gt;dyno&lt;/strong&gt;. Тобто, dyno - це ніби окремий віртуальний комп'ютер, що виконує вашу програму на серверах Heroku. &lt;/p&gt;

&lt;p&gt;Безкоштовно Heroku надає один dyno з наступними характеристиками:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;512 MB оперативної пам'яті&lt;/li&gt;
&lt;li&gt;550 годин виконання кожного місяця&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dyno бувають двох типів:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;web&lt;/strong&gt; - для програм що очікують вхідних HTTP запитів&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;worker&lt;/strong&gt; - для програм, що виконують операції у фоні&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5vkWXv47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/free-plan-capabilities.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5vkWXv47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/free-plan-capabilities.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Якщо ви використовуєте лише безкоштовний dyno, варто зауважити, що Heroku призупиняє виконання dyno після 30 хв бездіяльності. Тобто, якщо за останні 30 хв. до вашого web dyno не надійшло жодного запиту, він буде поставлений на паузу. Прокинеться він при наступному запиті. Для користувача це може виглядадти так, ніби сайт в браузері перший раз відкривається довго. В той час, поки ваш dyno спить, він НЕ споживає вільні години. Іншими словами, якщо ваш тестовий проєкт досить навантажений і обслуговує користувачів 24 години на добу вам вистачить вільних хвилин лише на 23 дні в місяць. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Heroku дає можливість розширити кількість вільних хвилин до 1000 просто вказавши реквізити банківської карти в налаштуваннях, без жодних списань коштів. Це дає можливість працювати одному free dyno без зупинки 24/7 або двом-трьом free dyno, якщо навантаження на сайт не постійне. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Налаштування Heroku CLI
&lt;/h2&gt;

&lt;p&gt;Давайте створимо безкоштовний акаунт &lt;a href="https://signup.heroku.com/"&gt;Heroku&lt;/a&gt; і розгорнемо в ньому просту ASP.NET Core програму. Після реєстрації і підтвердження поштової адреси завантажте &lt;a href="https://devcenter.heroku.com/articles/heroku-cli#download-and-install"&gt;Heroku CLI&lt;/a&gt;. Це консольна утиліта, що спростить створення і розгортання веб програми без відвідування сайту. Далі ми будемо все робити через командний рядок, тому важливо, щоб вона була встановлена.&lt;/p&gt;

&lt;p&gt;Перевірте, чи ви маєте все необхідне для роботи. Версії програм можуть відрізнятись, це не принципово. Головне, що .NET, Git and Heroku CLI встановлені коректно.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dotnet &lt;span class="nt"&gt;--version&lt;/span&gt;
3.1.300-preview-015115

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git &lt;span class="nt"&gt;--version&lt;/span&gt;
git version 2.24.1.windows.

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; heroku &lt;span class="nt"&gt;--version&lt;/span&gt;
heroku/7.30.0 win32-x64 node-v11.14.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Створення програми ASP.NET Core
&lt;/h2&gt;

&lt;p&gt;Створимо ASP.NET Core веб програму в каталозі &lt;code&gt;dotnet-app-heroku&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Вам не варто використовувати ту саму назву для програми. Змініть її на щось унікальне, наприклад &lt;code&gt;%USERNAME%-dotnet-app-heroku&lt;/code&gt;. Це дасть змогу уникнути конфліктів адрес з моєю програмою, яка може бути в цей момент розгорнута в Інтернет.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dotnet new webapp &lt;span class="nt"&gt;--name&lt;/span&gt; DotnetAppHeroku &lt;span class="nt"&gt;--output&lt;/span&gt; dotnet-app-heroku       
The template &lt;span class="s2"&gt;"ASP.NET Core Web App"&lt;/span&gt; was created successfully.
...
Restore succeeded.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер створимо програму в Heroku, але перед цим необхідно пройти процедуру логіну.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dotnet-app-heroku

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; heroku login   
heroku: Press any key to open up the browser to login or q to &lt;span class="nb"&gt;exit&lt;/span&gt;:
Opening browser to https://cli-auth.heroku.com/auth/cli/browser/12b5558d-f062-4dbb-a055-1c7e9f0f84c5
Logging &lt;span class="k"&gt;in&lt;/span&gt;... &lt;span class="k"&gt;done
&lt;/span&gt;Logged &lt;span class="k"&gt;in &lt;/span&gt;as elexander+heroku@ukr.net

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; heroku create dotnet-app-heroku
Creating ⬢ dotnet-app-heroku... &lt;span class="k"&gt;done
&lt;/span&gt;https://dotnet-app-heroku.herokuapp.com/ | https://git.heroku.com/dotnet-app-heroku.git

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В результаті команди &lt;code&gt;create&lt;/code&gt; ми отримали дві URL адреси, що знадобляться нам пізніше. Перша - це адреса нашого майбутнього сайту в Інтернет, а друга - адреса Git репозиторію, в який нам необхідно проштовхнути наш код, щоб розгорнути веб програму. &lt;/p&gt;

&lt;p&gt;Створимо git репозиторій в каталозі проєкту, додамо всі файли і спробуємо проштовхнути код у репозиторій.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git init
Initialized empty Git repository &lt;span class="k"&gt;in&lt;/span&gt; ./dotnet-app-heroku/.git/

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git remote add origin https://git.heroku.com/dotnet-app-heroku.git

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git add &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git commit &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Initial add"&lt;/span&gt;      
&lt;span class="o"&gt;[&lt;/span&gt;master &lt;span class="o"&gt;(&lt;/span&gt;root-commit&lt;span class="o"&gt;)&lt;/span&gt; f471c3c] Initial add
 57 files changed, 39806 insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
 create mode 100644 DotnetAppHeroku.csproj
 create mode 100644 Pages/Error.cshtml
 ...

 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git push origin master
 Enumerating objects: 76, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
...
Total 76 &lt;span class="o"&gt;(&lt;/span&gt;delta 12&lt;span class="o"&gt;)&lt;/span&gt;, reused 0 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;
remote: Compressing &lt;span class="nb"&gt;source &lt;/span&gt;files... &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
remote: Building &lt;span class="nb"&gt;source&lt;/span&gt;:
remote:
remote:  &lt;span class="o"&gt;!&lt;/span&gt;     No default language could be detected &lt;span class="k"&gt;for &lt;/span&gt;this app.
remote:  HINT: This occurs when Heroku cannot detect the buildpack to use &lt;span class="k"&gt;for &lt;/span&gt;this application 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Команда &lt;code&gt;git push&lt;/code&gt; звершилась з помилкою &lt;code&gt;No default language could be detected for this app&lt;/code&gt;. Причина в тому, що Heroku не підтримує .NET проєкти, тобто не знає, як зкомпілювати та запустити ASP.NET веб програму. Давайте допоможемо йому.&lt;/p&gt;

&lt;h2&gt;
  
  
  Контейнер в якості стеку
&lt;/h2&gt;

&lt;p&gt;Всі dyno за умовчанням запускаються на Linux Ubuntu 18:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LLFXPteW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/dotnet-app-heroku-settings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LLFXPteW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/dotnet-app-heroku-settings.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Але Heroku має можливість запустити будь-який &lt;strong&gt;Docker&lt;/strong&gt; контейнер і таким чином дозволяє самостійно вирішувати, яка операційна система і фреймворк потрібні програмісту. &lt;/p&gt;

&lt;p&gt;Для цього нам необхідно: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Змінити стек програми на &lt;code&gt;container&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Створити &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Вказати Heroku де цей &lt;code&gt;Dockerfile&lt;/code&gt; знаходиться
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; heroku stack:set container
Stack set. Next release on ⬢ dotnet-app-heroku will use container.
Run git push heroku master to create a new release on ⬢ dotnet-app-heroku.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Тепер dyno буде використовувати для запуску контейнер, який ми зараз побудуємо. Створіть файл &lt;code&gt;Dockerfile&lt;/code&gt; в каталозі проєкту з таким вмістом.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; *.csproj ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; out

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/core/aspnet:3.1&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build-env /app/out .&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; dotnet DotnetAppHeroku.dll&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ми не будемо зупинятись на детальному розборі даного файлу. Якщо коротко, то він компілює проєкт з релізною конфігурацією і запускає його на виконання командою &lt;code&gt;dotnet DotnetAppHeroku.dll&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Якщо ім'я вашого проєкту відрізняється від DotnetAppHeroku вам необхідно змінити останній рядоку файлу.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Також необхідно створити в корні проєкту файл &lt;code&gt;heroku.yml&lt;/code&gt;, де вказати шлях до &lt;code&gt;Dockerfile&lt;/code&gt;, який Heroku буде використовувати для побудови образу.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер можна додати новостворені файли до репозиторію і знову зробити push.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git add &lt;span class="nb"&gt;.&lt;/span&gt;  

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git status                              
On branch master
Changes to be committed:
  &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git restore --staged &amp;lt;file&amp;gt;..."&lt;/span&gt; to unstage&lt;span class="o"&gt;)&lt;/span&gt;        
        new file:   Dockerfile
        new file:   heroku.yml

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git commit &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Added Dockerfile"&lt;/span&gt; 
&lt;span class="o"&gt;[&lt;/span&gt;master 1f184b8] Added Dockerfile
 2 files changed, 15 insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;
 create mode 100644 Dockerfile
 create mode 100644 heroku.yml

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git push origin master
Enumerating objects: 80, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
...
remote: &lt;span class="o"&gt;===&lt;/span&gt; Building web &lt;span class="o"&gt;(&lt;/span&gt;Dockerfile&lt;span class="o"&gt;)&lt;/span&gt;
remote: Sending build context to Docker daemon  4.404MB    
...
remote: Verifying deploy... &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
To https://git.heroku.com/dotnet-app-heroku.git
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch]      master -&amp;gt; master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Змінна оточення PORT
&lt;/h2&gt;

&lt;p&gt;Якщо зараз відкрити сайт &lt;code&gt;https://dotnet-app-heroku.herokuapp.com/&lt;/code&gt; в браузері ми побачимо помилку. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fXsnCeZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/app-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fXsnCeZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/app-error.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Щоб розібратись, чому це сталося, переглянемо логи нашої програми:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; heroku logs
...
app[web.1]: Unhandled exception. System.Net.Sockets.SocketException &lt;span class="o"&gt;(&lt;/span&gt;13&lt;span class="o"&gt;)&lt;/span&gt;: Permission denied
...
app[web.1]: at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync&lt;span class="o"&gt;(&lt;/span&gt;EndPoint endpoint, CancellationToken cancellationToken&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Як бачимо, сталася помилка &lt;code&gt;Permission denied&lt;/code&gt; під час відкриття порту на прослуховування. Механізми безпеки Linux працюють таким чином, що для прослуховування стандартного для &lt;strong&gt;HTTP&lt;/strong&gt; порту &lt;strong&gt;80&lt;/strong&gt; процесу необхідні права супер-користувача. В &lt;a href="https://devcenter.heroku.com/articles/setting-the-http-port-for-java-applications"&gt;документації&lt;/a&gt; до Heroku можна знайти вирішення проблеми. Heroku очікує, що кожна програма буде стартувати не з довільним портом, а з портом заданим у змінній оточення &lt;strong&gt;PORT&lt;/strong&gt;. Внутрішні механізми Heroku будуть відправляти на цей порт весь трафік із зовнішнього порту 80, на якому сайт буде доступний в Інтернет. &lt;/p&gt;

&lt;p&gt;Виправимо це в нашому проєкті і додамо відповідну конфігурацію для Kestrel серверу у файлі &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;webBuilder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseStartup&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseKestrel&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAnyIP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер можна зберегти зміни і зробити пуш в репозиторій. Пуш займе близько хвилини, поки створиться Docker образ. Відкрийте &lt;code&gt;https://dotnet-app-heroku.herokuapp.com/&lt;/code&gt;. Тепер все працює.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ABKgZXrU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/app-running.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ABKgZXrU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-21-deploy-dotnet-core-app-for-free/app-running.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Висновок
&lt;/h2&gt;

&lt;p&gt;Ми побачили, як:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Створити безкоштовний акаунт в Heroku та інсталювати Heroku CLI.&lt;/li&gt;
&lt;li&gt;Створити проєкт ASP.NET Core та додати Dockerfile для компіляції проєкту в образ.&lt;/li&gt;
&lt;li&gt;Розгорнути образ в Heroku завдяки команді &lt;code&gt;git push&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Переглянути логи програми у разі помилки.&lt;/li&gt;
&lt;li&gt;Використовувати змінні оточення Heroku для успішного старту програми. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/alexmartyniuk/blog-dotnet-app-heroku"&gt;Репозиторій на Github&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Наступні кроки
&lt;/h2&gt;

&lt;p&gt;В наступних дописах я планую розглянути використання аддонів Heroku, які дають можливість підключати сервіси бази даних, кешування, логування, повнотекстового пошуку і багато чого іншого. Вони також мають безкоштовні версії, яких може бути цілком достатньо для вашого PET-проєкту.&lt;/p&gt;

&lt;p&gt;Також може бути цікавою документація по &lt;a href="https://devcenter.heroku.com/articles/heroku-cli-commands"&gt;Heroku CLI&lt;/a&gt;, яка дозволяє моніторити виконання ваших програм, переглядати кількість використаних годин, додавати інших співробітників до вашого проєкту і т.ін.&lt;/p&gt;




&lt;p&gt;Оригінальна стаття на &lt;a href="https://martyniuk.dev/uk/posts/%D0%B1%D0%B5%D0%B7%D0%BA%D0%BE%D1%88%D1%82%D0%BE%D0%B2%D0%BD%D0%B0-%D0%B1%D0%B4-%D0%B4%D0%BB%D1%8F-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%B8-aspnet-core/"&gt;моєму сайті&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspnet</category>
      <category>deployment</category>
      <category>heroku</category>
    </item>
    <item>
      <title>Жадібні алгоритми</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Sun, 10 May 2020 18:55:05 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/-4fh1</link>
      <guid>https://dev.to/olesmartyniuk/-4fh1</guid>
      <description>&lt;p&gt;Жадібний алгоритм - інтуїтивний та ефективний спосіб розв'язання задач оптимізації. І хоча його реалізація приваблює своєю очевидністю, він не завжди оптимальний. Необхідно точно розуміти, коли застосовувати жадібний підхід, а коли його варто уникати. В статті пропоную познайомитись з жадібними алгоритмами та потренуватись його застосовувати на задачі зі сайту &lt;a href="http://codility.com"&gt;codility.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Жадібний підхід
&lt;/h2&gt;

&lt;p&gt;Жадібні алгоритми - це ціле сімейство алгоритмів (інколи ще говорять про жадібний підхід або жадібне програмування), тому немає якогось конкретного жадібного алгоритму, який можна закодувати. Проте, всі жадібні алгоритми побудовані згідно одного принципу - обирати оптимальне вирішення на кожному кроці, не зважаючи на кроки, які були зроблені до або будуть зроблені після. Іншими словами, &lt;em&gt;жадібний алгоритм робить локально оптимальний вибір у сподіванні, що він приведе до глобально оптимального ровз'язку&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Задача про монети
&lt;/h2&gt;

&lt;p&gt;Розглянемо популярний приклад з монетками. Нехай у нас є монети номіналом 1, 2 і 5 копійок і нам необхідно відрахувати 10 копійок таким чином, щоб кількість монет була мінімальною. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KqdwFxZH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/CoinsWhiteBackground.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KqdwFxZH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/CoinsWhiteBackground.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;За логікою жадібного підходу на кожному кроці необхідно обирати монету з найбільшим номіналом і це приведе до мінімальної їх кількості в результаті. Візьмемо 5 копійок, тепер можемо взяти ще одну монету в 5 копійок, не вийшовши за обмеження у 10. В результаті 10 коп. = 5 коп. + 5 коп. Кількість монет - 2 і це, дійсно, мінімально можлива їх кількість за даних умов. &lt;/p&gt;

&lt;p&gt;Але якщо в задачу додати монету номіналом 6 копійок, це зробить застосування жадібного підходу не оптимальним. Дійсно, на першому кроці нам необхідно обрати 6, як монету з найбільшим номіналом, але далі ми не можемо обрати ні 6, ні 5, так як це перевищить ліміт у 10 копійок. Залишається дві монети по 2 коп. В результаті, ми маємо 10 як суму 6 + 2 + 2. В той час, як оптимальний розв'язок буде все ті ж 2 монети по 5.&lt;/p&gt;

&lt;p&gt;В залежності від проблеми яку ми розв'язуємо, жадібний метод може бути оптимальним, а може і не бути. Якщо він дає не оптимальний розв'язок, часто, він дозволяє знайти рішення близьке до оптимального. В такому випадку необхідно скористатись іншим підходом, наприклад, повним перебором або динамічним програмуванням. Але, якщо жадібний підхід все ж працює коректно, час виконання алгоритму буде значно меншим за час виконання повного перебору чи при динамічному програмуванні. &lt;/p&gt;

&lt;h2&gt;
  
  
  Переваги і недоліки
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Жадібний підхід легкий для розуміння і кодування.&lt;/strong&gt; На кожному кроці алгоритму ми можемо абстрагуватись від попередніх і наступних кроків і думати лише про оптимальний розв'язок на даному етапі. Жадібний підхід не передбачає відміну вже зробленого вибору (повернення на попередні кроки) і не прогнозує нічого на майбутнє.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Швидкість виконання програми при жадібному підході легко передбачити&lt;/strong&gt;, тому що складність алогритму очевидна. Найчастіше, вона лінійна, тобто, час виконання програми лінійно залежить від кількості вхідних даних. З іншими алгоритмічними підходами, такими як, наприклад, Розділяй і володарюй, це не завжди так.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Але у підходу є і великий недолік. &lt;strong&gt;У більшості випадків жадібний алгоритм працює некоректно.&lt;/strong&gt; Необхідно дуже добре розуміти, коли його можна використовувати, а коли - ні. І навіть якщо жадібний алгоритм дає оптимальне вирішення в певних випадках, важко довести, що підхід буде працювати у всіх іншим можливих випадках. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;В наведеному прикладі з монетами жадібний алгоритм добре працює для монет номіналом 1, 2, 5 але вже не працює для номіналів 1, 2, 5, 6. Варто зазаначити, що всі відомі мені грошові системи спроєктовані таким чином, що жадібний алгоритм працює для них коректно. Оскільки він простий і швидкий, люди легко знаходять потрібну суму для розрахунку в супермаркеті.&lt;/p&gt;

&lt;h2&gt;
  
  
  Правило застосування
&lt;/h2&gt;

&lt;p&gt;Існує евристичне правило для розуміння застосовності жадібного підходу. Якщо обидві властивості наведені нижче справджуються, жадібний алгоритм може бути застосований до розв'язання задачі.  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Принцип жадібного вибору&lt;/strong&gt;. Послідовність оптимальних виборів на кожному кроці приводить до оптимального рішення в кінці.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Оптимальна підструктура&lt;/strong&gt;. Задача має оптимальну підструктуру, якщо оптимальний розв'язок цілої задачі містить оптимальний розв'язок для будь якої підзадачі. Іншими словами, після заврешення певного кроку алгоритму залишається розв'язати задачу, для якої жадібний підхід також працює.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Спробуємо застосувати ці правила на прикладі &lt;strong&gt;Задачі про рюкзак&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Злодій проник на склад, де знаходяться три товари. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Товар&lt;/th&gt;
&lt;th&gt;Ціна&lt;/th&gt;
&lt;th&gt;Вага&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Товар А&lt;/td&gt;
&lt;td&gt;60 грн&lt;/td&gt;
&lt;td&gt;10 кг&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Товар Б&lt;/td&gt;
&lt;td&gt;100 грн&lt;/td&gt;
&lt;td&gt;20 кг&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Товар В&lt;/td&gt;
&lt;td&gt;120 грн&lt;/td&gt;
&lt;td&gt;30 кг&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Але у злодія є рюкзак лише на 50 кг. Яким чином він має вирішити, що взяти, щоб максимізувати свій прибуток?&lt;/p&gt;

&lt;p&gt;Не важко побачити, що оптимальним рішенням буде взяти товари Б і В, що в сумі дасть 220 грн. Але, якби злодій застосував жадібний підхід, він би почав обирати товари з найбільшою питомою вартістю (відношенням ціни до ваги товару). Найдорожчим товаром є А, оскільки він коштує 6 грн/кг, тоді як Б і В коштують 5 і 4 грн/кг відповідно. Тобто, жадібний злодій обрав би товари А і Б, як найдорожчі і таким чином зміг би забрати з собою лише 160 грн, так як товар В вже не вліз би у рюкзак.&lt;/p&gt;

&lt;p&gt;Повернемося до правила. Вибір першим товару А протирічить принципу жадібного відбору, адже не веде до оптимального рішення, яким є Б + В. Таким чином, жадібний алгоритм не може бути застосований в загальному випадку до Задачі про рюкзак. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Існує формальне доведення можливості або неможливості застосування жадібного алгоритму. Для цього необхідно звернутись до теорії &lt;a href="https://uk.wikipedia.org/wiki/%D0%9C%D0%B0%D1%82%D1%80%D0%BE%D1%97%D0%B4"&gt;Матроїдів&lt;/a&gt;.&lt;br&gt;
Якщо довести, що множина можливих розв'язків є матроїдом, згідно &lt;a href="https://uk.wikipedia.org/wiki/%D0%96%D0%B0%D0%B4%D1%96%D0%B1%D0%BD%D0%B8%D0%B9_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A0%D0%B0%D0%B4%D0%BE-%D0%95%D0%B4%D0%BC%D0%BE%D0%BD%D0%B4%D1%81%D0%B0"&gt;теореми Радо-Едмондса&lt;/a&gt;, до неї може бути успішно застосований жідібний підхід.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Використання на практиці
&lt;/h2&gt;

&lt;p&gt;Жадібні алгоритми мають багато застосуваннь. Одним з найвідоміших є &lt;a href="https://uk.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D0%B8"&gt;алгоритм Дейкстри&lt;/a&gt; для пошуку найкоротшого шляху у графі.&lt;/p&gt;

&lt;p&gt;Алгоритм працює з невідвіданими вузлами і обчислює орієнтовну відстань від даного вузла до іншого. Якщо алгоритм знаходить коротший спосіб дістатися до заданого вузла, шлях оновлюється з урахуванням коротшої відстані. Ця задача має оптимальну підструктуру, оскільки, якщо вузел A пов'язаний з B, а B пов'язаний з C, і шлях повинен пройти через A і B, щоб дістатися до пункту призначення C, то найкоротший шлях від A до B і найкоротший шлях від B до C має бути частиною найкоротшого шляху від A до C. Таким чином, оптимальне рішення підзадачі приводить до оптимального вирішення вцілому.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dCyH0Avt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/5/57/Dijkstra_Animation.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dCyH0Avt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://upload.wikimedia.org/wikipedia/commons/5/57/Dijkstra_Animation.gif" alt="Ілюстрація алгоритму Дейкстри"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://uk.wikipedia.org/wiki/%D0%9A%D0%BE%D0%B4_%D0%93%D0%B0%D1%84%D1%84%D0%BC%D0%B0%D0%BD%D0%B0"&gt;Кодування Гаффмана&lt;/a&gt; - інший відомий приклад успішного застосування жадібного підходу. Алгоритм Гаффмана аналізує деякий текст і присвоює кожному символу код змінної довжини на основі частоти, з якою він зустрічається. Символи, що зустрічаються найчастіше будуть мати коротші коди, символи, що зустрічаються рідко будуть мати довгі коди. Таким чином можна значно зменшити (інколи до 80%) кількість інформації необхідної для передачі або зберігання тексту.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Задача про розклад&lt;/strong&gt; також може бути розв'язана жадібно. Нехай у нас є кількість завдань, кожне з яких має певний дедлайн і винагороду. Виконання кожного завдання займає фіксований час. Винагорода буде виплачена, якщо завдання виконане до дедлайну. Необхідно обрати список завдань, щоб  максимізувати свій прибуток.&lt;/p&gt;

&lt;p&gt;Як бачимо, жадібний підхід може бути використаний в реальних задачах в різних областях, таких як складання розкладу, пошук оптимального шляху, кодування симолів та ін.&lt;/p&gt;

&lt;h2&gt;
  
  
  Задача з Codility
&lt;/h2&gt;

&lt;p&gt;На сайті &lt;a href="http://codility.com"&gt;codility.com&lt;/a&gt; є досить багато матеріалів по алгоритмам і, що добре для нас, тестові задачі, які допоможуть закріпити знання.&lt;/p&gt;

&lt;p&gt;Розв'яжемо задачу про відрізки, що не перетинаються, з уроку про жадібні алгоритми - &lt;a href="https://app.codility.com/programmers/lessons/16-greedy_algorithms/max_nonoverlapping_segments/"&gt;MaxNonoverlappingSegments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Отже, на координатній осі є відрізки, що задаються двома масивами A і B. Масив А містить координати початків, а B - координати кінців відрізків. Тобто, відрізок номер 1 починається в точці A[1] і закінчується в точці B[1]. &lt;/p&gt;

&lt;p&gt;Розглянемо такий приклад.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A[0] = 1    B[0] = 5
A[1] = 3    B[1] = 6
A[2] = 7    B[2] = 8
A[3] = 9    B[3] = 9
A[4] = 9    B[4] = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Відрізки показані на малюнку нижче.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--keiAo-lS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/TaskIllustration1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--keiAo-lS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/TaskIllustration1.png" alt="Ілюстрація до задачі"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Необхідно знайти максимальну кількість відрізків, які не перетинаються. Перетинаються ті відрізки, які мають хоча б одну спільну точку.&lt;/p&gt;

&lt;p&gt;Для прикладу вище максимальною кількістю відрізків, що не перетинаються, є 3. Можливі варіанти: &lt;code&gt;{0, 2, 3}&lt;/code&gt;, &lt;code&gt;{0, 2, 4}&lt;/code&gt;, &lt;code&gt;{1, 2, 3}&lt;/code&gt; або &lt;code&gt;{1, 2, 4}&lt;/code&gt;. Якщо взяти будь які 4 відрізки, то хоча б два з них обов'язково перетнуться.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Задача містить додаткову умову, яка впливає на її розв'язання - відрізки відсортовані за координатою кінця. Іншими словами, масив B впорядкований по зростанню.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Почнемо зі створення проєкту &lt;strong&gt;xunit&lt;/strong&gt; на C#.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xunit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MaxNonoverlappingSegments&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xUnit Test Project"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;successfully.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Я обрав саме проєкт з юніт тестами, так як ми спробуємо написати спочатку тест і тільки потім реалізацію алгортиму.&lt;/p&gt;

&lt;p&gt;Скопіюємо заготовку класу з сайту і самостійно створимо клас з тестом.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Solution&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;solution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnitTest1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Theory&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
        &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;solution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Через атрибут &lt;code&gt;InlineData&lt;/code&gt; задаються тестові дані. В даному випадку для порожнього списку очікується 0 відрізків, що не перетинаються.&lt;/p&gt;

&lt;p&gt;Зпустимо тест. Він завершився з помилкою &lt;code&gt;NotImplementedException&lt;/code&gt;, адже клас &lt;code&gt;Solution&lt;/code&gt; не реалізований. Давайте, на першому етапі як повернемо просто загальну кількість відрізків. Це повинно спрацювати.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;solution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер тест завершився успішно.&lt;/p&gt;

&lt;p&gt;Додамо також тест для одного відрізку і для двох відрізків, які не перетинаються.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Наш код все ще задовольняє цим тестам! &lt;/p&gt;

&lt;p&gt;Але для ситуації двох відрізків, які перетинаються, алгоритм вже не працює.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MaxNonoverlappingSegments.UnitTest1.Test(a: [1, 2], b: [2, 3], result: 1) [FAIL]
[xUnit.net 00:00:00.63]       Assert.Equal() Failure
[xUnit.net 00:00:00.63]       Expected: 1
[xUnit.net 00:00:00.63]       Actual:   2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер, власне, настав час реалізувати жадібний алгоритм вибору відрізків. Ідея, яка першою приходить в голову - рухаючись з права на ліво обирати перший доступний відрізок і запам'ятовувати його. Якщо наступний відрізок перетинається з поточним пропустимо його, якщо ж не перетинається, то збільшимо лічильник відрізків. Після цього необхідно запам'ятати наступний відрізок як поточний і рухатись далі.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Даний алгоритм можна застосувати, адже відрізки за умовою відсортовані за координатою кінця.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;В коді це можна виразити наступним чином:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;solution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;--)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Якщо бути точним, тут не запам'ятовується поточний відрізок, а лише координата його початку (змінна position), адже цього достатньо, щоб перевірити, чи наступний відрізок може бути включений у розв'язок. Якщо координата кінця поточного відрізку менша за position, він додається до результату і position змінюється на його початок.&lt;/p&gt;

&lt;p&gt;Чудово, тепер всі тести проходять! Додамо ще випадок описаний на сайті:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Він теж виконується успішно, але не поспішаймо відправляти результат. Наш алгоритм дозволяє зменшити проміжки між відрізками, але він не бере до уваги довжину відрізків. Якщо перший відрізок, який нам потрапить буде займати всю координатну вісь, алгоритм проігнорує всі інші відрізки. А серед них можуть бути декілька таких, що не перетинаються між собою. Тому наш алгоритм явно НЕ оптимальний. Цей тест описує такий випадок:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InlineData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CXf-b0iQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/TaskIllustration2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CXf-b0iQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/TaskIllustration2.png" alt="Ілюстрація до задачі"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Є три відрізки. Відрізок 1-5 найдовший і перекриває всі інші відрізки. Так як його кінець розташований найдалі, при жадібному підході він буде обраний першим і не дасть змоги включити інші відрізки в розв'язок. Замість правильного результату 2 (відрізки 1-2, 3-4) ми отримаємо в результаті 1 (відрізок 1-5).&lt;/p&gt;

&lt;p&gt;Якщо ми відсортуємо відрізки по координаті початку і спробуємо йти від початку до кінця, то отримаємо ту ж саму проблему, але з іншого боку. Тому це не варіант.&lt;/p&gt;

&lt;p&gt;Нам необхідно змінити функцію оптимальності, яка застосовується на кожному кроці. Якщо ми натрапили на відрізок, що коротший за поточний і все ще повністю ним перекривається, необхідно змінити поточний відрізок на більш короткий. Таким чином ми мінімізуємо довжину відрізків і максимізуємо їх кількість.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;solution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;--)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Перша частина алгоритму залишилась незмінною, якщо ми натрапляємо на відрізок, що може бути включений у розв'язок, інкрементуємо result і переходимо до наступного відрізку.&lt;/p&gt;

&lt;p&gt;Якщо ми натрапили на відрізок, що повністю перекривається поточним  (&lt;code&gt;if (A[i] &amp;gt; position)&lt;/code&gt;), замінимо поточний відрізок на коротший.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total tests: 6. Passed: 6. Failed: 0. Skipped: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Всі тести завершились успішно. Тепер можемо відправити даний код на перевірку.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TUBy5MEa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/CodilityResultsDetailed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TUBy5MEa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://martyniuk.dev/assets/img/posts/2020-05-08-greedy-algorithms/CodilityResultsDetailed.png" alt="Результат виконання задачі"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Як бачите, наш алгоритм не тільки коректний, він ще й оптимальний за часом. Його складність лінійна O(n).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Тестові приклади, які пропонує сайт &lt;a href="http://codility.com"&gt;codility.com&lt;/a&gt; не завжди покривають всі випадки, тому навіть якщо ваш код добре працює для пропонованих тестових даних спробуйте проаналізувати ваш розв'язок і уявити ситуації, в яких алгоритм може не спрацювати і створіть такі додаткові тести.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Висновок
&lt;/h2&gt;

&lt;p&gt;Жадібний підхід - чудовий інтуїтивний і ефективний спосіб розв'язання багатьох популярних задач програмування. Його найбільшим недоліком є необхідність добре розуміти, коли він може бути застосований, а коли необхідно звернутись до іншого підходу. Але навіть в ситуаціях, коли жадібний алгоритм не дає оптимального рішення, його результат може бути близьким до оптимального і він точно буде ефективнішим за метод повного перебору або динамічне програмування.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://martyniuk.dev/uk/posts/greedy-algorithms/"&gt;Оригінальна стаття&lt;/a&gt; на моєму сайті.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>ukrainian</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>LINQ в C#. Огляд</title>
      <dc:creator>Oleksandr Martyniuk</dc:creator>
      <pubDate>Fri, 01 May 2020 16:59:34 +0000</pubDate>
      <link>https://dev.to/olesmartyniuk/linq-c-bi6</link>
      <guid>https://dev.to/olesmartyniuk/linq-c-bi6</guid>
      <description>&lt;p&gt;В цій статті пропоную розглянути LINQ як важливий компонент .NET фреймворку, його історію та роль. Чому він був створений і як врешті користуватись цим інструментом. В кінці розглянемо приклади на мові C#, які дадуть уявлення про те, що таке LINQ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LINQ&lt;/strong&gt; - (&lt;em&gt;анг. language integrated query&lt;/em&gt;) - мова запитів до структурованих даних, що інтегрована в C#. Такими структурованими даними можуть бути колекції об'єктів в пам'яті, XML файли, таблиці баз даних, веб-сервіси і т.ін. Але для чого нам потрібен ще один інструмент в мові C#, яка і так дозволяє працювати з даними? Вся справа у легкості розуміння і сприйняття. Для того, щоб пояснити в чому саме полягає легкість LINQ, необхідно розглянути відмінність між декларативним та імперативним програмуванням.&lt;/p&gt;

&lt;h2&gt;
  
  
  Імперативне та декларативне програмування
&lt;/h2&gt;

&lt;p&gt;Перші мови програмування задавали чіткий порядок команд. Це дуже зручно, коли ви маєте справу з регістрами процесору і прямим доступом до пам'яті. В таких мовах, як C та Assembler широко використовуються оператори виділення пам'яті, присвоєння, умовні оператори та підпрограми. Все це ознаки імперативного підходу. Слово &lt;em&gt;імператив&lt;/em&gt; з англійської перекладається як &lt;em&gt;наказ&lt;/em&gt; і це досить точне вмзначення для подібного підходу, тому що при імперативному підході програма є послідовністю чітких команд і комп'ютер виконує ці команди одна за одною.&lt;/p&gt;

&lt;p&gt;До імперативних мов програмування відносяться: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C#&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Всі ці мови базуються на змінних, операторах присвоєння і підпрограмах. Такий підхід близький до того як працює комп'ютер, але далекий від людської мови, звичної всім нам. Людині зручніше декларувати те, чого вона хоче, ніж описувати чіткий алгоритм досягнення цієї мети. &lt;/p&gt;

&lt;p&gt;Тому імперативному підходу протиставляється декларативний. Його головною ознакою є те, що покроковий алгоритм не задається. Натомість задається джерело даних, описується бажаний результат і набір правил, завдяки яким цей результат буде досягнуто. Виконання запиту доручається інтерпретатору, який вміє перевести його у форму зручну для комп'ютера, тобто в імперативну форму.&lt;/p&gt;

&lt;p&gt;До декларативних мов відносяться:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Regular Expressions&lt;/li&gt;
&lt;li&gt;XSLT Transformation&lt;/li&gt;
&lt;li&gt;Gremlin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Саме введення декларативного підходу до обробки даних в .NET і було головною метою створення LINQ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Порівняння підходів
&lt;/h2&gt;

&lt;p&gt;Розглянемо на прикладі два підходи, спочатку імперативний з використанням циклу, а потім декларативний з використанням LINQ.&lt;/p&gt;

&lt;p&gt;Як тестовий набір даних візьмемо список супергероїв, кожен з яких має ім'я, рік народження (або першої згадки у коміксах) та назву серії, де він вперше з'явився.&lt;/p&gt;

&lt;p&gt;Отже, створимо нову консольну програму в .NET Core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt;  LinqTestApp
The template &lt;span class="s2"&gt;"Console Application"&lt;/span&gt; was created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Визначимо клас супергероя і додамо тестові дані.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hero&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;YearOfBirth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Comics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_heroes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Superman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;YearOfBirth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1938&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Comics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Action Comics"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Batman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;YearOfBirth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1938&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Comics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Detective Comics"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Captain America"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;YearOfBirth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1941&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Comics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Captain America Comics"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ironman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;YearOfBirth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1963&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Comics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Tales of Suspense"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Hero&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Spiderman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;YearOfBirth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1963&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Comics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Amazing Fantasy"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Нехай, наша задача полягатиме в тому, щоб відібрати тих супергероїв, які мають слово "man" в імені та вивести їх імена на екран у алфавітному порядку. Тобто, ми повинні отримати імена всіх героїв (окрім Капітана Америки) у відсортованому вигляді.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Batman
Ironman
Spiderman
Superman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;При імперативному підході нам необхідно створити список в який ми будемо заносити імена відфільтрованих героїв, потім пройтись по списку героїв і додати до створеного списку лише тих, чиї імена містять слово "man". Після цього необхідно відсортувати отриманий список і вивести на екран:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;heroNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_heroes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"man"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;heroNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;heroNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;heroName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;heroNames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heroName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Тепер давайте розв'яжемо ту ж задачу, але за допомогою LINQ. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Не забудьте піключити простір імен &lt;code&gt;System.Linq&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;heroNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_heroes&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"man"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;orderby&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;heroNames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Програма стала на 5 рядків коротшою і легшою для розуміння, адже вибірка даних здійснюється мовою дуже схожою на англійську: &lt;em&gt;From heroes where heroName contains "man" ordered by name select name&lt;/em&gt;, що можна перекласти як &lt;em&gt;"З колекції героїв обери імена тих героїв які містять слово "man" в імені і відсортуй їх за ім'ям"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Розберемо, що робить даний код.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;from hero in _heroes&lt;/code&gt; задає джерело даних. У нас це константний список &lt;code&gt;_heroes&lt;/code&gt;. До кожного елементу списку ми будемо звертатися у виразі через змінну &lt;code&gt;hero&lt;/code&gt;. І хоча ми ніде не вказували її тип, вираз залишається строго типізованим, адже компілятор має змогу вивести тип з типу колекції &lt;code&gt;_heroes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;where hero.Name.Contains&lt;/code&gt; задає умову фільтрації вхідного списку. Якщо елемент відповідає умові, він передається далі. Таким чином, всі подальші оператори у виразі будуть працювати вже з відфільтрованим списком. Оператор &lt;code&gt;where&lt;/code&gt; ще називають оператором фільтрації.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;orderby hero.Name&lt;/code&gt; задає поле і спосіб сортування.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;select hero.Name&lt;/code&gt; задає значення, що потрапить у результуючу вибірку. Оскільки нас цікавлять лише імена героїв, ми вказуємо тут поле Name. Саме цей оператор задає тип результату, тому у нашому випадку це буде &lt;code&gt;IEnumerable&amp;lt;string&amp;gt;&lt;/code&gt;. Даний оператор ще називають оператором проекції, оскільки він перетворює дані, що містить джерело у вигляд необхідний нам для конкретної задачі. &lt;/p&gt;

&lt;h2&gt;
  
  
  Методи розширення
&lt;/h2&gt;

&lt;p&gt;Варто зазначити, що хоча синтаксис LINQ значно відрізняється від синтаксису C#, все ж під капотом LINQ використовує методи розширення C#, тож наведений вище запит можна переписати в більш звичному об'єктному стилі:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;heroNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_heroes&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"man"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Такий синтаксис називається синтаксисом методів розширення (або лямбда синтаксисом) і він може застосовуватись разом з синтаксисом запитів LINQ. Часто, коли говорять про LINQ мають на увазі методи розширення, тому що вони реалізовані в просторі імен &lt;code&gt;System.Linq&lt;/code&gt; і є частиною LINQ як компоненту .NET. У своїй більшості ці методи розширюють інтерфейс IEnumerable і є базою для реалізації LINQ. Це може спочатку збивати з пантелику, але LINQ це не лише синтаксис &lt;code&gt;from ... in ... select&lt;/code&gt;, але також і синтаксис методів розширення.&lt;/p&gt;

&lt;p&gt;Так як методи розширення є базою для реалізації LINQ, вони більш потужні ніж синтаксис запитів. Наприклад, метод &lt;code&gt;Where&lt;/code&gt; має перевантажену версію, в якій при фільтрації доступний індекс елементу у вихідній колекції. Цей індекс може бути використаний при формуванні логічного виразу (предикату).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"man"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Нажаль, даний індекс недоступний, якщо ми використовуємо синтаксис запиту &lt;code&gt;from ... in ... select&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;При використанні синтаксису запиту також недоступні скалярні функції Count, Max, Sum та інші методи (наприклад, Intersect). &lt;/p&gt;

&lt;p&gt;Також, з методами розширення ми можемо розбити LINQ вираз на декілька частин і сформувати його згідно певної умови, що для синтаксису запиту неможливо. Наприклад:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_heroes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"man"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shouldBeSorted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;heroNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hero&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ми додаємо сортування тільки якщо вхідний параметр &lt;code&gt;shouldBeSorted&lt;/code&gt; дорівнює &lt;code&gt;true&lt;/code&gt;. Використовуючи синтаксис запиту нам необхідно записати вираз двічі в залежності від умови: в першому випадку зі сортуванням, а в другому - без нього.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;В подальшому огляді ми будемо використовувати синтаксис методів розширення, так як він більш потужний і дозволяє показати можливості LINQ в повній мірі.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Трохи історії
&lt;/h2&gt;

&lt;p&gt;У 2007 році мова C# мала версію 2.0 і не мала LINQ. Обробка даних відбувалась в імперативному стилі. В той час вже існували Python 2.4 та JavaScript 1.6, які мали потужні вбудовані засоби роботи з колекціями, такі як &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt; і &lt;code&gt;reduce&lt;/code&gt;. C# значно програвав їм у зручності коли йшлося про роботу з колекціями, і це не могло продовжуватись довго.&lt;/p&gt;

&lt;p&gt;Восени 2007 року компанія Microsoft випустила .NET Framework 3.5 в якому були значні нововведення. Ці зміни дали можливість створити LINQ та підняти версію мови C# до 3.0 Серед нововведень були:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Лямбда&lt;/strong&gt; вирази зробили можливим просте визначення предикатів для методів типу Where, Select у вигляді &lt;code&gt;() =&amp;gt; {...}&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Анонімні типи&lt;/strong&gt; дозволили створювати об'єкти довільної структури на льоту і прибрали необхідність оголошувати тип для результату LINQ виразу.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Дерева виразів&lt;/strong&gt; зробили можливим збереження предикатів у вигляді об'єктів, на основі яких різні провайдери даних могли сформувати власний оптимізований запит. Це стосується LINQ to SQL або LINQ to XPath. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Методи розширення&lt;/strong&gt; дозволили розширяти вже існуючі типи без їх модифікації і наслідування. Це дозволило застосовувати LINQ до великої кількості сторонніх типів, що підтримують IEnumerable або IQueryable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ініціалізатори об’єктів та колекцій&lt;/strong&gt; довзолили створювати об'єкти та ініціалізувати їх поля без використання конструкторів, що значно спростило синтаксис для методів проекції LINQ, коли нові об'єкти створюються як частина виразу.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Оголошення змінних через &lt;code&gt;var&lt;/code&gt;&lt;/strong&gt; значно спростило визначення типу результату запиту і зробило можливим повернення даних анонімних типів з LINQ виразу.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Всі ці можливості вивели C# і платформу .NET на якісно новий рівень і довзолили створити LINQ. З моменту випуску він став невід'ємною частиною .NET фреймворку і як бібліотека для роботи з даними не поступається, а багато в чому і перевершує, вбудовані засоби інших мов, таких як Java, Python, Go і JavaScript.&lt;/p&gt;

&lt;p&gt;В наступних статтях розглянемо детальніше всі основні аспекти роботи з LINQ. Ви переконаєтесь, що це не тільки зручний, але й доволі ефективний та зрілий інструмент.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://martyniuk.dev/uk/posts/linq-overview/"&gt;Оригінальна стаття&lt;/a&gt; на моєму сайті.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>linq</category>
      <category>dotnet</category>
      <category>ukrainian</category>
    </item>
  </channel>
</rss>
